Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cyd",
"private": true,
"version": "1.0.22",
"version": "1.0.24-dev",
"main": ".vite/build/main.js",
"description": "Automatically delete your data from tech platforms, except for what you want to keep",
"license": "proprietary",
Expand Down
10 changes: 7 additions & 3 deletions src/account_x/x_account_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1718,13 +1718,17 @@ export class XAccountController {
// Unzip twitter archive to the account data folder using unzipper
// Return unzipped path if success, else null.
async unzipXArchive(archiveZipPath: string): Promise<string | null> {
const archiveZip = await unzipper.Open.file(archiveZipPath);
if (!this.account) {
return null;
}
const unzippedPath = path.join(getAccountDataPath("X", this.account.username), path.parse(archiveZipPath).name)
const unzippedPath = path.join(getAccountDataPath("X", this.account.username), "tmp");

const archiveZip = await unzipper.Open.file(archiveZipPath);
await archiveZip.extract({ path: unzippedPath });
return unzippedPath

log.info(`XAccountController.unzipXArchive: unzipped to ${unzippedPath}`);

return unzippedPath;
}

// Delete the unzipped X archive once the build is completed
Expand Down
35 changes: 12 additions & 23 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
nativeImage,
autoUpdater,
powerSaveBlocker,
powerMonitor
powerMonitor,
FileFilter
} from 'electron';
import { updateElectronApp, UpdateSourceType } from 'update-electron-app';

Expand Down Expand Up @@ -343,34 +344,22 @@ async function createWindow() {
}
});

ipcMain.handle('showSelectZIPFileDialog', async (_): Promise<string | null> => {
ipcMain.handle('showOpenDialog', async (_, selectFolders: boolean, selectFiles: boolean, fileFilters: FileFilter[] | undefined = undefined): Promise<string | null> => {
const dataPath = database.getConfig('dataPath');

const options: Electron.OpenDialogSyncOptions = {
filters: [{ name: 'Archive', extensions: ['zip'] }],
properties: ['openFile'],
};

if (dataPath) {
options.defaultPath = dataPath;
const properties: ("openFile" | "openDirectory" | "multiSelections" | "showHiddenFiles" | "createDirectory" | "promptToCreate" | "noResolveAliases" | "treatPackageAsDirectory" | "dontAddToRecent")[] = [];
if (selectFolders) {
properties.push('openDirectory');
properties.push('createDirectory');
properties.push('promptToCreate');
}

try {
const result = dialog.showOpenDialogSync(win, options);
if (result && result.length > 0) {
return result[0];
}
return null;
} catch (error) {
throw new Error(packageExceptionForReport(error as Error));
if (selectFiles) {
properties.push('openFile');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had actually tried implementing the first time I was implementing this feature, but found out that this doesn't work with Windows and Linux.

From the docs:

Note: On Windows and Linux an open dialog can not be both a file selector and a directory selector, so if you set properties to ['openFile', 'openDirectory'] on these platforms, a directory selector will be shown.

So basically when I test it in Linux, that selector is just a folder selector, and I can't select zip files. I think the solution might be to give 2 different selectors (which I know looks terrible, but I don't know a way around).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eeek. I only tested in macOS. Okay, I'll make changes.

}
});

ipcMain.handle('showSelectFolderDialog', async (_): Promise<string | null> => {
const dataPath = database.getConfig('dataPath');

const options: Electron.OpenDialogSyncOptions = {
properties: ['openDirectory', 'createDirectory', 'promptToCreate'],
properties: properties,
filters: fileFilters,
};
if (dataPath) {
options.defaultPath = dataPath;
Expand Down
9 changes: 3 additions & 6 deletions src/preload.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { contextBridge, ipcRenderer } from 'electron'
import { contextBridge, ipcRenderer, FileFilter } from 'electron'
import {
ErrorReport,
Account,
Expand Down Expand Up @@ -51,11 +51,8 @@ contextBridge.exposeInMainWorld('electron', {
showQuestion: (message: string, trueText: string, falseText: string): Promise<boolean> => {
return ipcRenderer.invoke('showQuestion', message, trueText, falseText)
},
showSelectZIPFileDialog: (): Promise<string | null> => {
return ipcRenderer.invoke('showSelectZIPFileDialog')
},
showSelectFolderDialog: (): Promise<string | null> => {
return ipcRenderer.invoke('showSelectFolderDialog')
showOpenDialog: (selectFolders: boolean, selectFiles: boolean, fileFilters: FileFilter[] | undefined = undefined): Promise<string | null> => {
return ipcRenderer.invoke('showOpenDialog', selectFolders, selectFiles, fileFilters)
},
openURL: (url: string) => {
ipcRenderer.invoke('openURL', url)
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import type {
} from "../../shared_types";
import App from "./App.vue";

import { FileFilter } from "electron";

declare global {
interface Window {
electron: {
Expand All @@ -38,8 +40,7 @@ declare global {
showMessage: (message: string) => void;
showError: (message: string) => void;
showQuestion: (message: string, trueText: string, falseText: string) => Promise<boolean>;
showSelectZIPFileDialog: () => Promise<string | null>;
showSelectFolderDialog: () => Promise<string | null>;
showOpenDialog: (selectFolders: boolean, selectFiles: boolean, fileFilters: FileFilter[] | undefined) => Promise<string | null>;
openURL: (url: string) => void;
loadFileInWebview: (webContentsId: number, filename: string) => void;
getAccountDataPath: (accountID: number, filename: string) => Promise<string | null>,
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/modals/AdvancedSettingsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let modalInstance: Modal | null = null;
const dataPath = ref('');

const browseClicked = async () => {
const newDataPath = await window.electron.showSelectFolderDialog();
const newDataPath = await window.electron.showOpenDialog(true, false, undefined);
if (newDataPath) {
dataPath.value = newDataPath;
await window.electron.database.setConfig('dataPath', newDataPath);
Expand Down
5 changes: 2 additions & 3 deletions src/renderer/src/test_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ export const stubElectron = () => {
shouldOpenDevtools: cy.stub().resolves(false),
showMessage: cy.stub(),
showError: cy.stub(),
showQuestion: cy.stub().resolves(true),
showSelectZIPFileDialog: cy.stub().resolves(null),
showSelectFolderDialog: cy.stub().resolves(null),
showQuestion: cy.stub(),
showOpenDialog: cy.stub(),
openURL: cy.stub(),
loadFileInWebview: cy.stub(),
getAccountDataPath: cy.stub().resolves(null),
Expand Down
89 changes: 72 additions & 17 deletions src/renderer/src/views/x/XWizardImportingPage.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script setup lang="ts">
import {
ref,
getCurrentInstance
getCurrentInstance,
onMounted
} from 'vue';
import {
AccountXViewModel,
Expand All @@ -25,6 +26,10 @@ const emit = defineEmits<{
setState: [value: State]
}>()

// Keep track of platform
// Mac users can browse for both ZIP or unzipped folder at once, but Windows and Linux users need two separate buttons
const platform = ref('');

// Buttons
const backClicked = async () => {
emit('setState', State.WizardImportStart);
Expand Down Expand Up @@ -53,21 +58,42 @@ const createCountString = (importCount: number, skipCount: number) => {
const startClicked = async () => {
errorMessages.value = [];
importStarted.value = true;
let unzippedPath: string | null = null;

// Does importFromArchivePath end with .zip?
if (!importFromArchivePath.value.endsWith('.zip')) {
unzippedPath = importFromArchivePath.value;
} else {
// Unarchive the zip
statusValidating.value = ImportStatus.Active;
try {
unzippedPath = await window.electron.X.unzipXArchive(props.model.account.id, importFromArchivePath.value);
} catch (e) {
statusValidating.value = ImportStatus.Failed;
errorMessages.value.push(`${e}`);
importFailed.value = true;
return;
}
if (unzippedPath === null) {
statusValidating.value = ImportStatus.Failed;
errorMessages.value.push(unzippedPath);
importFailed.value = true;
return;
}
}

// Unarchive the zip
// Verify that the archive is valid
statusValidating.value = ImportStatus.Active;
const unzippedPath: string | null = await window.electron.X.unzipXArchive(props.model.account.id, importFromArchivePath.value);
if (unzippedPath === null) {
let verifyResp: string | null = null;
try {
verifyResp = await window.electron.X.verifyXArchive(props.model.account.id, unzippedPath);
} catch (e) {
statusValidating.value = ImportStatus.Failed;
errorMessages.value.push(unzippedPath);
errorMessages.value.push(`${e}`);
importFailed.value = true;
await window.electron.X.deleteUnzippedXArchive(props.model.account.id, unzippedPath);
return;
}
statusValidating.value = ImportStatus.Finished;

// Verify that the archive is valid
statusValidating.value = ImportStatus.Active;
const verifyResp: string | null = await window.electron.X.verifyXArchive(props.model.account.id, unzippedPath);
if (verifyResp !== null) {
statusValidating.value = ImportStatus.Failed;
errorMessages.value.push(verifyResp);
Expand Down Expand Up @@ -128,8 +154,22 @@ const startClicked = async () => {

};

const importFromArchiveBrowserClicked = async () => {
const path = await window.electron.showSelectZIPFileDialog();
const importFromArchiveBrowseClicked = async () => {
const path = await window.electron.showOpenDialog(true, true, [{ name: 'ZIP Archive', extensions: ['zip'] }]);
if (path) {
importFromArchivePath.value = path;
}
};

const importFromArchiveBrowseZipClicked = async () => {
const path = await window.electron.showOpenDialog(false, true, [{ name: 'ZIP Archive', extensions: ['zip'] }]);
if (path) {
importFromArchivePath.value = path;
}
};

const importFromArchiveBrowseFolderClicked = async () => {
const path = await window.electron.showOpenDialog(true, false, undefined);
if (path) {
importFromArchivePath.value = path;
}
Expand Down Expand Up @@ -170,6 +210,10 @@ const iconFromStatus = (status: ImportStatus) => {
}
};

onMounted(async () => {
platform.value = await window.electron.getPlatform();
});

</script>

<template>
Expand All @@ -179,7 +223,8 @@ const iconFromStatus = (status: ImportStatus) => {
</h2>
<p class="text-muted">
<template v-if="!importStarted">
Browse for the ZIP file of the X archive you downloaded.
Browse for the ZIP file of the X archive you downloaded, or the folder where you have already extracted
it.
</template>
<template v-else>
Importing your archive...
Expand All @@ -190,9 +235,19 @@ const iconFromStatus = (status: ImportStatus) => {
<div class="input-group">
<input v-model="importFromArchivePath" type="text" class="form-control"
placeholder="Import your X archive" readonly>
<button class="btn btn-secondary" @click="importFromArchiveBrowserClicked">
Browse for Archive
</button>
<template v-if="platform == 'darwin'">
<button class="btn btn-secondary" @click="importFromArchiveBrowseClicked">
Browse for Archive
</button>
</template>
<template v-else>
<button class="btn btn-secondary me-1" @click="importFromArchiveBrowseZipClicked">
Browse for ZIP
</button>
<button class="btn btn-secondary" @click="importFromArchiveBrowseFolderClicked">
Browse for Unzipped Folder
</button>
</template>
</div>

<div class="buttons">
Expand All @@ -216,7 +271,7 @@ const iconFromStatus = (status: ImportStatus) => {
<i v-else>
<RunningIcon />
</i>
Unzipping and validating X archive
Validating X archive
</li>
<li :class="statusImportingTweets == ImportStatus.Pending ? 'text-muted' : ''">
<i v-if="statusImportingTweets != ImportStatus.Active"
Expand Down
Loading