Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Commit

Permalink
Merge pull request #4 from Mavahu/dev
Browse files Browse the repository at this point in the history
v1.2.0 release
  • Loading branch information
Mavahu committed Jul 15, 2020
2 parents 4a99895 + 3a34b38 commit edf39b4
Show file tree
Hide file tree
Showing 14 changed files with 619 additions and 445 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ $ npm run package
- [x] Sharelink creation
- [x] Handle saving/resetting
- [x] Files sorting
- [ ] Drag and drop files
- [x] Drag and drop files/folders
- [ ] Upload/Download settings
- [ ] Implement account-handle check
- [x] Implement account-handle check
- [ ] Account informations
- [ ] Batch requests
- [ ] About me window
Binary file modified assets/readme-picture.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 16 additions & 6 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,21 @@ function createMainWindow() {

app.on('ready', () => {
createMainWindow();

const mainMenu = Menu.buildFromTemplate(menu);
Menu.setApplicationMenu(mainMenu);
});

const menu = [
...(isMac ? [{ role: 'appMenu' }] : []),
{ role: 'fileMenu' },
{
label: 'Handle',
submenu: [{ label: 'Reset Handle', click: () => resetHandle() }],
label: app.name,
submenu: [
{
label: 'Reset Handle',
click: () => resetHandle(),
},
isMac ? { role: 'close' } : { role: 'quit' },
],
},
...(isDev
? [
Expand Down Expand Up @@ -117,7 +121,14 @@ ipcMain.on('login:restore', async (e) => {

ipcMain.on('handle:set', async (e, handleObject) => {
try {
if (handleObject.handle.length !== 128) {
throw Error("Then handle doesn't have the right length of 128 signs.");
}

await setAccount(handleObject.handle);
// Call this function before saving the handle to see if the entered handle is correct
// eg. mixed up letters etc.
await refreshFolder('/');

if (handleObject.saveHandle) {
// save handle to keyring
Expand All @@ -127,7 +138,7 @@ ipcMain.on('handle:set', async (e, handleObject) => {
mainWindow.webContents.send('login:success');
refreshFolder('/');
} catch (err) {
console.log(err);
mainWindow.webContents.send('login:failed', { error: err.message });
}
});

Expand All @@ -136,7 +147,6 @@ ipcMain.on('path:update', async (e, newPath) => {
});

ipcMain.on('files:delete', async (e, files) => {
console.log('deleting');
for (const file of files) {
if (await account.delete(file.folder, file.handle, file.name)) {
refreshFolder(file.folder);
Expand Down
21 changes: 9 additions & 12 deletions opacity/OpacityAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class OpacityAccount extends EventEmitter {
Buffer.from(this.privateKey, 'hex'),
Buffer.from(this.chainCode, 'hex')
);
// donwloadMutex and uploadMutex allow only 1 down- and upload simultaniously
// TODO: Change them to semaphores, and add the initiation value to the constructor
this.downloadMutex = new Mutex();
this.downloadChunksSemaphore = new Semaphore(10);
this.uploadMutex = new Mutex();
Expand Down Expand Up @@ -98,6 +100,7 @@ class OpacityAccount extends EventEmitter {
hashedFolderKey,
keyString
);

return {
metadata: fmd,
hashedFolderKey: hashedFolderKey,
Expand All @@ -110,17 +113,17 @@ class OpacityAccount extends EventEmitter {
timestamp: Date.now(),
metadataKey: hashedFolderKey,
};
const rawPayloadJson = JSON.stringify(rawPayload);

const rawPayloadJson = JSON.stringify(rawPayload);
const payload = this._signPayload(rawPayloadJson);
const payloadJson = JSON.stringify(payload);

const response = await Axios.post(
this.baseUrl + 'metadata/get',
payloadJson
);
const encryptedMetadata = Buffer.from(response.data.metadata, 'base64');

const encryptedMetadata = Buffer.from(response.data.metadata, 'base64');
const decrypted = Utils.decrypt(
encryptedMetadata,
Buffer.from(keyString, 'hex')
Expand Down Expand Up @@ -211,7 +214,7 @@ class OpacityAccount extends EventEmitter {
payloadJson
);

// Delete the folder now remove the folder from the parent folder
// Delete the folder now by removing the folder from the parent folder

const newFolders = metadata.metadata.folders.filter(
(folder) => folder.handle !== handle
Expand Down Expand Up @@ -243,7 +246,6 @@ class OpacityAccount extends EventEmitter {
};

const rawPayloadJson = JSON.stringify(rawPayload);

const payload = this._signPayload(rawPayloadJson);
const payloadJson = JSON.stringify(payload);

Expand Down Expand Up @@ -550,7 +552,7 @@ class OpacityAccount extends EventEmitter {
}

// Download all parts
console.log(`Downloading file: ${fileMetadata.name}`);
//console.log(`Downloading file: ${fileMetadata.name}`);
const fileDownloadUrl = downloadUrl + '/file';

const promises = [];
Expand All @@ -571,7 +573,7 @@ class OpacityAccount extends EventEmitter {
);
});
await Promise.allSettled(promises);
console.log('Total time: ' + (Date.now() - time) / 1000);
//console.log('Total time: ' + (Date.now() - time) / 1000);

// Reconstruct file out of the parts
console.log('Reconstructing');
Expand All @@ -593,7 +595,6 @@ class OpacityAccount extends EventEmitter {
for (let chunkIndex = 0; chunkIndex < chunksAmount; chunkIndex++) {
let chunkRawBytes;
let toReadBytes = chunkSize;
//myBinaryFile.seek(seek);
if (seek + toReadBytes >= Fs.statSync(partPath).size) {
toReadBytes = Fs.statSync(partPath).size - seek;
seek = 0;
Expand All @@ -608,7 +609,6 @@ class OpacityAccount extends EventEmitter {
}
const decryptedChunk = Utils.decryptFileChunk(chunkRawBytes, fileKey);
await outputFile.write(decryptedChunk);
// Fs.appendFileSync(savePath, decryptedChunk, { encoding: 'binary' });

if (seek === 0 && chunkIndex + 1 !== chunksAmount) {
await myBinaryFile.close();
Expand Down Expand Up @@ -827,10 +827,7 @@ class OpacityAccount extends EventEmitter {
return true;
} else if (item.handle.length === 64) {
const oldFolderPath = Utils.getSlash(Path.join(fromFolder, item.name));
if (
oldFolderPath ===
toFolder.slice(0, oldFolderPath.length)
) {
if (oldFolderPath === toFolder.slice(0, oldFolderPath.length)) {
throw Error(`Error: ${fromFolder} is a parent folder of ${toFolder}`);
}
const newFolderPath = Utils.getSlash(Path.join(toFolder, item.name));
Expand Down
2 changes: 1 addition & 1 deletion 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": "opacity-electron",
"productName": "OpacityApp",
"version": "1.1.0",
"version": "1.2.0",
"description": "Desktop Application for the Opacity cloud",
"author": "Martin V. Hubert",
"license": "MIT",
Expand Down
198 changes: 198 additions & 0 deletions src/components/ActionButtons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { ipcRenderer } from 'electron';
const { dialog } = require('electron').remote;
import React, { useState } from 'react';
import Button from 'react-bootstrap/Button';
import ButtonGroup from 'react-bootstrap/ButtonGroup';
import Card from 'react-bootstrap/Card';
import Swal from 'sweetalert2';

const ActionButtons = ({
metadata,
folderPath,
massButtons,
downloadFunc,
changeAllCheckboxState,
}) => {
const defaultCutButton = {
cut: true,
files: [],
folder: '',
};
const [cutButton, setCutButton] = useState(
JSON.parse(JSON.stringify(defaultCutButton))
);

async function downloadSelected() {
const toDownload = [];
metadata.folders.map((folder) => {
if (folder.checked) {
toDownload.push({ name: folder.name, handle: folder.handle });
}
});
metadata.files.map((file) => {
if (file.checked) {
toDownload.push({ name: file.name, handle: file.versions[0].handle });
}
});
await downloadFunc(toDownload);
}

async function cutAndPaste(paste = true) {
if (cutButton.cut) {
const filesToMove = [];
metadata.folders.map((folder) => {
if (folder.checked) {
filesToMove.push({ handle: folder.handle, name: folder.name });
}
});
metadata.files.map((file) => {
if (file.checked) {
filesToMove.push({
handle: file.versions[0].handle,
name: file.name,
});
}
});
setCutButton({ cut: false, folder: folderPath, files: filesToMove });
changeAllCheckboxState(false);
} else {
if (paste && cutButton.folder !== folderPath) {
ipcRenderer.send('files:move', {
fromFolder: cutButton.folder,
files: cutButton.files,
toFolder: folderPath,
});
} else if (!paste) {
console.log('Moving cancelled');
} else if (cutButton.folder === folderPath) {
console.log('Tried to drop into the origin folder');
}
setCutButton(JSON.parse(JSON.stringify(defaultCutButton)));
}
}

async function deleteSelected() {
const checkedFolders = metadata.folders.filter((folder) => folder.checked);
const checkedFiles = metadata.files.filter((file) => file.checked);
const { value: result } = await Swal.fire({
title: 'Are you sure?',
html: `You won't be able to revert this!<br/>Folders: ${checkedFolders
.map((folder) => '<li>' + folder.name + '</li>')
.join('')}<br/>Files: ${checkedFiles
.map((file) => '<li>' + file.name + '</li> ')
.join('')}`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, delete it!',
});

if (result) {
const toDelete = [];
checkedFolders.map((folder) =>
toDelete.push({
folder: folderPath,
handle: folder.handle,
name: folder.name,
})
);
checkedFiles.map((file) =>
toDelete.push({
folder: folderPath,
handle: file.versions[0].handle,
name: file.name,
})
);
ipcRenderer.send('files:delete', toDelete);
changeAllCheckboxState(false);
}
}

async function newFolder() {
const { value: folderName } = await Swal.fire({
title: 'Enter the folder name',
input: 'text',
showCancelButton: true,
cancelButtonColor: '#d33',
inputValidator: (value) => {
if (!value) {
return 'You need to write something!';
}
},
});

if (folderName) {
Swal.fire('', `Created folder: ${folderName}`, 'success');
ipcRenderer.send('folder:create', {
parentFolder: folderPath,
folderName: folderName,
});
}
}

function uploadButton(isFolder = false) {
console.log(isFolder);
dialog
.showOpenDialog({
properties: [
isFolder ? 'openDirectory' : 'openFile',
'multiSelections',
],
})
.then((result) => {
if (!result.canceled) {
ipcRenderer.send('files:upload', {
folder: folderPath,
files: result.filePaths,
});
}
})
.catch((err) => {
console.log(err);
});
}

return (
<ButtonGroup>
<Card>
<Button disabled={!massButtons} onClick={() => downloadSelected()}>
Download
</Button>
</Card>
<Card>
{cutButton.cut ? (
<Button disabled={!massButtons} onClick={() => cutAndPaste()}>
Cut
</Button>
) : (
<ButtonGroup>
<Button
disabled={cutButton.folder === folderPath}
onClick={() => cutAndPaste()}
>
Paste
</Button>
<Button onClick={() => cutAndPaste(false)}>Cancel</Button>
</ButtonGroup>
)}
</Card>
<Card className="mr-1">
<Button disabled={!massButtons} onClick={() => deleteSelected()}>
Delete
</Button>
</Card>
<Card className="mr-1">
<Button onClick={() => newFolder()}>Create Folder</Button>
</Card>
<Card>
<Button onClick={() => uploadButton(true)}>Upload Folder</Button>
</Card>
<Card>
<Button onClick={() => uploadButton(false)}>Upload Files</Button>
</Card>
</ButtonGroup>
);
};

export default ActionButtons;

0 comments on commit edf39b4

Please sign in to comment.