Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Download Manager #1700

Merged
merged 77 commits into from
Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
2388ae6
TrayIcon show and hide on focus
TaimurAzhar May 3, 2020
4d12049
downlads page created
TaimurAzhar Jun 1, 2020
5a709db
Merge branch 'new-page' into feat/new-page
TaimurAzhar Jun 1, 2020
fb9c20b
downloads icon and icon position
TaimurAzhar Jun 1, 2020
47b3888
title and subtitle
TaimurAzhar Jun 2, 2020
1ba42ed
basic download item
TaimurAzhar Jun 2, 2020
92929e3
download item ui rough
TaimurAzhar Jun 13, 2020
579c360
progress-bar working
TaimurAzhar Jun 22, 2020
bb8f411
added search and select inputs
TaimurAzhar Jun 23, 2020
65305fa
download-item-ui nearly done
TaimurAzhar Jun 26, 2020
b9505a8
attach webcontent id to server
TaimurAzhar Jun 26, 2020
d5d1d33
server title added to download
TaimurAzhar Jun 27, 2020
9f12425
insatlled electron-store
TaimurAzhar Jun 27, 2020
31b53c5
download store initial
TaimurAzhar Jun 27, 2020
fafd8da
downloads list loading
TaimurAzhar Jun 27, 2020
ee28661
cleaning up - checkpoint commit
TaimurAzhar Jun 27, 2020
cac8dd2
show in folder button
TaimurAzhar Jul 1, 2020
53c9e90
Remove downloads autoclearing
TaimurAzhar Jul 2, 2020
daa694f
currentServeUrl reset to normal
TaimurAzhar Jul 2, 2020
637b2d4
pause, resume, cancel
TaimurAzhar Jul 13, 2020
2f0eb09
search by filename filter
TaimurAzhar Jul 14, 2020
c5d357c
server and filetype filters testing
TaimurAzhar Jul 15, 2020
13fc3aa
all tabs working
TaimurAzhar Jul 18, 2020
9525554
retry downloads
TaimurAzhar Jul 28, 2020
8fdf3c6
date headings and filter by filetype
TaimurAzhar Jul 29, 2020
f890371
fixing small eslint codechecks
TaimurAzhar Jul 29, 2020
ac0d82c
basic compact view
TaimurAzhar Jul 30, 2020
0cfb859
Review
ggazzo Jul 31, 2020
26d1810
Review
ggazzo Jul 31, 2020
b11d639
fix cancelled status bug
TaimurAzhar Aug 12, 2020
70224b5
tooltips for buttons
TaimurAzhar Aug 12, 2020
9d27776
added status.all constant
TaimurAzhar Aug 12, 2020
aa5cbb3
finished status constant
TaimurAzhar Aug 12, 2020
3924945
seperated item layout components
TaimurAzhar Aug 12, 2020
c54c6e1
modals for destructive actions
TaimurAzhar Aug 12, 2020
e63bf5c
initial thumnail images only
TaimurAzhar Aug 14, 2020
ec08084
added useMutabelCallbacks
TaimurAzhar Aug 14, 2020
bdf52b3
fixed network speed estimate
TaimurAzhar Aug 15, 2020
de5d5d1
added keyboard shortcut and window-menu option
TaimurAzhar Aug 19, 2020
b1ee32a
event string and estimated time left
TaimurAzhar Aug 19, 2020
5974b82
cancel from dialog box bug fix
TaimurAzhar Aug 19, 2020
5c677e0
filename change bug fix
TaimurAzhar Aug 19, 2020
7e52f6e
unimported module removed
TaimurAzhar Aug 19, 2020
aecfea5
Merge branch 'feat/gsoc-month3' of https://github.com/TaimurAzhar/Roc…
TaimurAzhar Aug 19, 2020
63eee86
code cleanup
TaimurAzhar Aug 19, 2020
5b9a7b2
progress bar changes color
TaimurAzhar Aug 22, 2020
4a52139
fixed speed showing while cancelled
TaimurAzhar Aug 24, 2020
eedded4
progress bar colors changed
TaimurAzhar Aug 26, 2020
cddc4c5
Merge pull request #1630 from TaimurAzhar/feat/downloads-items-ui
tassoevan Aug 29, 2020
3caf169
Merge pull request #1678 from TaimurAzhar/feat/search-downloads
tassoevan Aug 29, 2020
a69a285
Merge pull request #1680 from RocketChat/TaimurAzhar/feat/search-down…
tassoevan Aug 29, 2020
2a0755a
changed delete button text
TaimurAzhar Aug 30, 2020
3c288ae
removed logs
TaimurAzhar Aug 30, 2020
2077f69
key prop warning
TaimurAzhar Aug 30, 2020
ed1cc2d
Merge branch 'gsoc/download_manager' into feat/gsoc-month3
TaimurAzhar Aug 30, 2020
c6899a8
Merge branch 'feat/gsoc-month3' of https://github.com/TaimurAzhar/Roc…
TaimurAzhar Aug 30, 2020
d3c0871
small fixes
TaimurAzhar Aug 30, 2020
695eba2
Merge pull request #1691 from TaimurAzhar/feat/gsoc-month3
tassoevan Aug 30, 2020
92ef395
New UI Design
MartinSchoeler Sep 25, 2020
c82122e
Update Fuselage
tassoevan Nov 4, 2020
467bf35
Undo some comments
tassoevan Nov 4, 2020
462cc8b
Rearrange components
tassoevan Nov 4, 2020
556fc77
Move code to downloads module
tassoevan Nov 4, 2020
de90a57
Merge branch 'master' of github.com:RocketChat/Rocket.Chat.Electron i…
tassoevan Nov 4, 2020
de6c213
Convert modules to TypeScript
tassoevan Nov 5, 2020
6c3e03a
Replace currentServerUrl with currentView
tassoevan Nov 9, 2020
8bfbb0e
Rollback accidental addition of dependencies
tassoevan Nov 10, 2020
ccb2636
Add more number formatters
tassoevan Nov 12, 2020
d965b48
Delegate download handling to the main process
tassoevan Nov 12, 2020
9b8d805
Update Jest configuration
tassoevan Nov 12, 2020
cd2bbd0
Remove unused module
tassoevan Nov 12, 2020
73fb570
Fix fsevents issue affecting Jest
tassoevan Nov 12, 2020
082d004
Update filters
tassoevan Nov 12, 2020
47b0743
Rollback fsevents resolution
tassoevan Nov 12, 2020
630b8d1
Rollback fsevents resolution
tassoevan Nov 12, 2020
c2aa06a
Merge branch 'master' of github.com:RocketChat/Rocket.Chat.Electron i…
tassoevan Nov 14, 2020
281bada
Merge branch 'master' of github.com:RocketChat/Rocket.Chat.Electron i…
tassoevan Dec 1, 2020
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
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@
"@bugsnag/js": "^7.3.5",
"@emotion/core": "^10.0.35",
"@emotion/styled": "^10.0.27",
"@rocket.chat/fuselage": "^0.15.1",
"@rocket.chat/fuselage-hooks": "^0.15.1",
"@rocket.chat/icons": "^0.15.1",
"@rocket.chat/css-in-js": "^0.17.2",
"@rocket.chat/fuselage": "^0.17.2",
"@rocket.chat/fuselage-hooks": "^0.17.2",
"@rocket.chat/icons": "^0.17.2",
"abort-controller": "^3.0.0",
"electron-store": "^6.0.0",
"electron-updater": "^4.3.5",
Expand Down Expand Up @@ -71,7 +72,6 @@
"@rollup/plugin-replace": "^2.3.3",
"@rollup/plugin-typescript": "^6.0.0",
"@types/electron-devtools-installer": "^2.2.0",
"@types/i18next-node-fs-backend": "^2.1.0",
"@types/jest": "^26.0.14",
"@types/meteor": "^1.4.49",
"@types/node": "^12",
Expand All @@ -86,7 +86,7 @@
"@typescript-eslint/parser": "^4.2.0",
"babel-eslint": "^10.1.0",
"builtin-modules": "^3.1.0",
"chokidar": "^3.4.2",
"chokidar": "^3.4.3",
"conventional-changelog-cli": "^2.1.0",
"convert-svg-to-png": "^0.5.0",
"electron": "^10.0.1",
Expand All @@ -97,7 +97,7 @@
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-react": "^7.21.1",
"eslint-plugin-react-hooks": "^4.1.0",
"jest": "^26.4.2",
"jest": "^26.6.3",
"jimp": "^0.16.1",
"npm-run-all": "^4.1.5",
"puppeteer": "^5.5.0",
Expand All @@ -108,6 +108,9 @@
"typescript": "^4.0.3",
"xvfb-maybe": "^0.2.1"
},
"optionalDependencies": {
"fsevents": "2.2.1"
},
"engines": {
"node": ">=12.8.x"
},
Expand Down
10 changes: 5 additions & 5 deletions src/app/actions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { downloads } from '../downloads/reducers/downloads';
import {
externalProtocols,
trustedCertificates,
} from '../navigation/reducers';
import {
currentServerUrl,
servers,
} from '../servers/reducers';
import { servers } from '../servers/reducers';
import { currentView } from '../ui/reducers/currentView';
import { isMenuBarEnabled } from '../ui/reducers/isMenuBarEnabled';
import { isShowWindowOnUnreadChangedEnabled } from '../ui/reducers/isShowWindowOnUnreadChangedEnabled';
import { isSideBarEnabled } from '../ui/reducers/isSideBarEnabled';
Expand All @@ -28,8 +27,9 @@ export type AppActionTypeToPayloadMap = {
[APP_PATH_SET]: string;
[APP_VERSION_SET]: string;
[APP_SETTINGS_LOADED]: {
currentServerUrl: ReturnType<typeof currentServerUrl>;
currentView: ReturnType<typeof currentView>;
doCheckForUpdatesOnStartup: ReturnType<typeof doCheckForUpdatesOnStartup>;
downloads: ReturnType<typeof downloads>;
externalProtocols: ReturnType<typeof externalProtocols>;
isEachUpdatesSettingConfigurable: ReturnType<typeof isEachUpdatesSettingConfigurable>;
isMenuBarEnabled: ReturnType<typeof isMenuBarEnabled>;
Expand Down
12 changes: 11 additions & 1 deletion src/app/main/persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ import { selectPersistableValues } from '../selectors';

type PersistableValues = ReturnType<typeof selectPersistableValues>;

const migrations = {};
const migrations = {
'>=3.1.0': (store: ElectronStore<PersistableValues & { currentServerUrl: string }>) => {
if (!store.has('currentServerUrl')) {
return;
}

const currentServerUrl = store.get('currentServerUrl');
store.set('currentView', currentServerUrl ? { url: currentServerUrl } : 'add-new-server');
store.delete('currentServerUrl');
},
};

let electronStore: ElectronStore<PersistableValues>;

Expand Down
3 changes: 2 additions & 1 deletion src/app/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { RootState } from '../store/rootReducer';
import { APP_SETTINGS_LOADED } from './actions';

export const selectPersistableValues = createStructuredSelector<Partial<RootState>, ActionOf<typeof APP_SETTINGS_LOADED>['payload']>({
currentServerUrl: ({ currentServerUrl }) => currentServerUrl,
currentView: ({ currentView }) => currentView,
doCheckForUpdatesOnStartup: ({ doCheckForUpdatesOnStartup }) => doCheckForUpdatesOnStartup,
downloads: ({ downloads }) => downloads,
isMenuBarEnabled: ({ isMenuBarEnabled }) => isMenuBarEnabled,
isShowWindowOnUnreadChangedEnabled: ({ isShowWindowOnUnreadChangedEnabled }) => isShowWindowOnUnreadChangedEnabled,
isSideBarEnabled: ({ isSideBarEnabled }) => isSideBarEnabled,
Expand Down
13 changes: 13 additions & 0 deletions src/downloads/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Download } from './common';

export const DOWNLOAD_CREATED = 'downloads/created';
export const DOWNLOAD_REMOVED = 'dowloads/removed';
export const DOWNLOADS_CLEARED = 'downloads/cleared';
export const DOWNLOAD_UPDATED = 'downloads/updated';

export type DownloadsActionTypeToPayloadMap = {
[DOWNLOAD_CREATED]: Download;
[DOWNLOAD_UPDATED]: Pick<Download, 'itemId'> & Partial<Download>;
[DOWNLOAD_REMOVED]: Download['itemId'];
[DOWNLOADS_CLEARED]: void;
}
23 changes: 23 additions & 0 deletions src/downloads/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Server } from '../servers/common';

export const DownloadStatus = {
ALL: 'All',
PAUSED: 'Paused',
CANCELLED: 'Cancelled',
} as const;

export type Download = {
itemId: number;
state: 'progressing' | 'paused' | 'completed' | 'cancelled' | 'interrupted';
status: typeof DownloadStatus[keyof typeof DownloadStatus];
fileName: string;
receivedBytes: number;
totalBytes: number;
startTime: number;
endTime: number | undefined;
url: string;
serverUrl: Server['url'];
serverTitle: Server['title'];
savePath: string;
mimeType: string;
};
186 changes: 186 additions & 0 deletions src/downloads/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import path from 'path';

import { clipboard, DownloadItem, Event, shell, WebContents, webContents } from 'electron';

import { handle } from '../ipc/main';
import { dispatch, select } from '../store';
import {
DOWNLOAD_CREATED,
DOWNLOAD_REMOVED,
DOWNLOAD_UPDATED,
} from './actions';
import { Download, DownloadStatus } from './common';

const items = new Map<Download['itemId'], DownloadItem>();

export const handleWillDownloadEvent = async (_event: Event, item: DownloadItem, serverWebContents: WebContents): Promise<void> => {
const itemId = Date.now();

items.set(itemId, item);

const fileName = item.getFilename();

const extension = path.extname(fileName)?.slice(1).toLowerCase();

if (extension) {
item.setSaveDialogOptions({
filters: [
{
name: `*.${ extension }`,
extensions: [extension],
},
{
name: '*.*',
extensions: ['*'],
},
],
});
}

const server = select(({ servers }) => servers.find((server) => server.webContentsId === serverWebContents.id));

dispatch({
type: DOWNLOAD_CREATED,
payload: {
itemId,
state: item.isPaused() ? 'paused' : item.getState(),
status: item.isPaused() ? DownloadStatus.PAUSED : DownloadStatus.ALL,
fileName: item.getFilename(),
receivedBytes: item.getReceivedBytes(),
totalBytes: item.getTotalBytes(),
startTime: item.getStartTime() * 1000,
endTime: undefined,
url: item.getURL(),
serverUrl: server?.url,
serverTitle: server?.title,
mimeType: item.getMimeType(),
savePath: item.getSavePath(),
},
});

item.on('updated', () => {
dispatch({
type: DOWNLOAD_UPDATED,
payload: {
itemId,
state: item.isPaused() ? 'paused' : item.getState(),
status: item.isPaused() ? DownloadStatus.PAUSED : DownloadStatus.ALL,
fileName: item.getFilename(),
receivedBytes: item.getReceivedBytes(),
totalBytes: item.getTotalBytes(),
startTime: item.getStartTime() * 1000,
endTime: Date.now(),
url: item.getURL(),
mimeType: item.getMimeType(),
savePath: item.getSavePath(),
},
});
});

item.on('done', () => {
dispatch({
type: DOWNLOAD_UPDATED,
payload: {
itemId,
state: item.isPaused() ? 'paused' : item.getState(),
status: item.getState() === 'cancelled' ? DownloadStatus.CANCELLED : DownloadStatus.ALL,
fileName: item.getFilename(),
receivedBytes: item.getReceivedBytes(),
totalBytes: item.getTotalBytes(),
startTime: item.getStartTime() * 1000,
endTime: Date.now(),
url: item.getURL(),
mimeType: item.getMimeType(),
savePath: item.getSavePath(),
},
});

items.delete(itemId);
});
};

export const setupDownloads = (): void => {
handle('downloads/show-in-folder', async (_webContents, itemId) => {
const download = select(({ downloads }) => downloads[itemId]);

if (!download) {
return;
}

shell.showItemInFolder(download.savePath);
});

handle('downloads/copy-link', async (_webContent, itemId) => {
const download = select(({ downloads }) => downloads[itemId]);

if (!download) {
return;
}

clipboard.write({ text: download.url });
});

handle('downloads/pause', async (_webContent, itemId) => {
if (!items.has(itemId)) {
return;
}

const item = items.get(itemId);

if (item.isPaused()) {
return;
}

item.pause();
});

handle('downloads/resume', async (_webContent, itemId) => {
if (!items.has(itemId)) {
return;
}

const item = items.get(itemId);

if (!item.canResume()) {
return;
}

item.resume();
});

handle('downloads/cancel', async (_webContent, itemId) => {
if (!items.has(itemId)) {
return;
}

const item = items.get(itemId);
item.cancel();
});

handle('downloads/retry', async (_webContent, itemId) => {
const { url, webContentsId } = select(({ downloads, servers }) => {
const { url, serverUrl } = downloads[itemId];
const { webContentsId } = servers.find((server) => server.url === serverUrl);
return { url, webContentsId };
});

dispatch({
type: DOWNLOAD_REMOVED,
payload: itemId,
});

webContents.fromId(webContentsId).downloadURL(url);
});

handle('downloads/remove', async (_webContent, itemId) => {
if (items.has(itemId)) {
const item = items.get(itemId);
item.cancel();
}

dispatch({
type: DOWNLOAD_REMOVED,
payload: itemId,
});
});
};
56 changes: 56 additions & 0 deletions src/downloads/reducers/downloads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { APP_SETTINGS_LOADED } from '../../app/actions';
import { ActionOf } from '../../store/actions';
import {
DOWNLOADS_CLEARED,
DOWNLOAD_CREATED,
DOWNLOAD_REMOVED,
DOWNLOAD_UPDATED,
} from '../actions';
import { Download } from '../common';

type DownloadsAction = (
ActionOf<typeof APP_SETTINGS_LOADED>
| ActionOf<typeof DOWNLOAD_CREATED>
| ActionOf<typeof DOWNLOAD_UPDATED>
| ActionOf<typeof DOWNLOADS_CLEARED>
| ActionOf<typeof DOWNLOAD_REMOVED>
);

export const downloads = (
state: Record<Download['itemId'], Download> = {},
action: DownloadsAction,
): Record<Download['itemId'], Download> => {
switch (action.type) {
case APP_SETTINGS_LOADED:
return action.payload.downloads ?? {};

case DOWNLOAD_CREATED: {
const download = action.payload;
return {
...state,
[download.itemId]: download,
};
}

case DOWNLOAD_UPDATED: {
const newState = { ...state };
newState[action.payload.itemId] = {
...newState[action.payload.itemId],
...action.payload,
};
return newState;
}

case DOWNLOAD_REMOVED: {
const newState = { ...state };
delete newState[action.payload];
return newState;
}

case DOWNLOADS_CLEARED:
return {};

default:
return state;
}
};
Loading