* Use downloads location as default when clicking "Save as". Remove update from downloads after upgrade when application starts
* Fix issue where "addedAt" was extracted from undefined object
* Fix tests
(cherry picked from commit ca62814ce6
)
Co-authored-by: Tasos Boulis <tboulis@hotmail.com>
This commit is contained in:
parent
dd7a3f9fd5
commit
36f9afe74c
|
@ -31,9 +31,9 @@ export default class JsonFileManager<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setJson(json: T): Promise<void> {
|
setJson(json: T): void {
|
||||||
this.json = json;
|
this.json = json;
|
||||||
await this.writeToFile();
|
this.writeToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(key: keyof T, value: T[keyof T]): void {
|
setValue(key: keyof T, value: T[keyof T]): void {
|
||||||
|
|
|
@ -1,8 +1,22 @@
|
||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {DownloadedItem} from 'types/downloads';
|
||||||
|
|
||||||
|
import {DownloadItemTypeEnum} from 'main/downloadsManager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This string includes special characters so that it's not confused with
|
* This string includes special characters so that it's not confused with
|
||||||
* a file that may have the same filename (eg APP_UPDATE)
|
* a file that may have the same filename (eg APP_UPDATE)
|
||||||
*/
|
*/
|
||||||
export const APP_UPDATE_KEY = '#:(APP_UPDATE):#';
|
export const APP_UPDATE_KEY = '#:(APP_UPDATE):#';
|
||||||
|
|
||||||
|
export const UPDATE_DOWNLOAD_ITEM: Omit<DownloadedItem, 'filename' | 'state'> = {
|
||||||
|
type: 'update' as DownloadItemTypeEnum,
|
||||||
|
progress: 0,
|
||||||
|
location: '',
|
||||||
|
mimeType: null,
|
||||||
|
addedAt: 0,
|
||||||
|
receivedBytes: 0,
|
||||||
|
totalBytes: 0,
|
||||||
|
};
|
||||||
|
|
|
@ -58,6 +58,7 @@ export class UpdateManager {
|
||||||
lastCheck?: NodeJS.Timeout;
|
lastCheck?: NodeJS.Timeout;
|
||||||
versionAvailable?: string;
|
versionAvailable?: string;
|
||||||
versionDownloaded?: string;
|
versionDownloaded?: string;
|
||||||
|
downloadedInfo?: UpdateInfo;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.cancellationToken = new CancellationToken();
|
this.cancellationToken = new CancellationToken();
|
||||||
|
@ -76,6 +77,7 @@ export class UpdateManager {
|
||||||
|
|
||||||
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
|
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
|
||||||
this.versionDownloaded = info.version;
|
this.versionDownloaded = info.version;
|
||||||
|
this.downloadedInfo = info;
|
||||||
ipcMain.emit(UPDATE_SHORTCUT_MENU);
|
ipcMain.emit(UPDATE_SHORTCUT_MENU);
|
||||||
log.info(`[Mattermost] downloaded version ${info.version}`);
|
log.info(`[Mattermost] downloaded version ${info.version}`);
|
||||||
this.notifyDownloaded();
|
this.notifyDownloaded();
|
||||||
|
@ -115,7 +117,7 @@ export class UpdateManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyDownloaded = (): void => {
|
notifyDownloaded = (): void => {
|
||||||
ipcMain.emit(UPDATE_DOWNLOADED, null, this.versionDownloaded);
|
ipcMain.emit(UPDATE_DOWNLOADED, null, this.downloadedInfo);
|
||||||
displayRestartToUpgrade(this.versionDownloaded || 'unknown', this.handleUpdate);
|
displayRestartToUpgrade(this.versionDownloaded || 'unknown', this.handleUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,8 +143,8 @@ export class UpdateManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdate = async (): Promise<void> => {
|
handleUpdate = (): void => {
|
||||||
await downloadsManager.removeUpdateBeforeRestart();
|
downloadsManager.removeUpdateBeforeRestart();
|
||||||
autoUpdater.quitAndInstall();
|
autoUpdater.quitAndInstall();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,7 @@ import fs from 'fs';
|
||||||
|
|
||||||
import {DownloadItem, Event, WebContents, FileFilter, ipcMain, dialog, shell, Menu, app} from 'electron';
|
import {DownloadItem, Event, WebContents, FileFilter, ipcMain, dialog, shell, Menu, app} from 'electron';
|
||||||
import log from 'electron-log';
|
import log from 'electron-log';
|
||||||
import {ProgressInfo} from 'electron-updater';
|
import {ProgressInfo, UpdateInfo} from 'electron-updater';
|
||||||
|
|
||||||
import {DownloadedItem, DownloadItemDoneEventState, DownloadedItems, DownloadItemState, DownloadItemUpdatedEventState} from 'types/downloads';
|
import {DownloadedItem, DownloadItemDoneEventState, DownloadedItems, DownloadItemState, DownloadItemUpdatedEventState} from 'types/downloads';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -26,15 +25,15 @@ import {
|
||||||
UPDATE_PROGRESS,
|
UPDATE_PROGRESS,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
|
import JsonFileManager from 'common/JsonFileManager';
|
||||||
|
import {APP_UPDATE_KEY, UPDATE_DOWNLOAD_ITEM} from 'common/constants';
|
||||||
|
import {DOWNLOADS_DROPDOWN_AUTOCLOSE_TIMEOUT, DOWNLOADS_DROPDOWN_MAX_ITEMS} from 'common/utils/constants';
|
||||||
import {localizeMessage} from 'main/i18nManager';
|
import {localizeMessage} from 'main/i18nManager';
|
||||||
import {displayDownloadCompleted} from 'main/notifications';
|
import {displayDownloadCompleted} from 'main/notifications';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
import WindowManager from 'main/windows/windowManager';
|
||||||
import {doubleSecToMs, getPercentage, isStringWithLength, readFilenameFromContentDispositionHeader, shouldIncrementFilename} from 'main/utils';
|
import {doubleSecToMs, getPercentage, isStringWithLength, readFilenameFromContentDispositionHeader, shouldIncrementFilename} from 'main/utils';
|
||||||
import {DOWNLOADS_DROPDOWN_AUTOCLOSE_TIMEOUT, DOWNLOADS_DROPDOWN_MAX_ITEMS} from 'common/utils/constants';
|
|
||||||
import JsonFileManager from 'common/JsonFileManager';
|
|
||||||
|
|
||||||
import {APP_UPDATE_KEY} from 'common/constants';
|
|
||||||
|
|
||||||
|
import appVersionManager from './AppVersionManager';
|
||||||
import {downloadsJson} from './constants';
|
import {downloadsJson} from './constants';
|
||||||
import * as Validator from './Validator';
|
import * as Validator from './Validator';
|
||||||
|
|
||||||
|
@ -46,11 +45,9 @@ export enum DownloadItemTypeEnum {
|
||||||
export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
autoCloseTimeout: NodeJS.Timeout | null;
|
autoCloseTimeout: NodeJS.Timeout | null;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
||||||
fileSizes: Map<string, string>;
|
fileSizes: Map<string, string>;
|
||||||
progressingItems: Map<string, DownloadItem>;
|
progressingItems: Map<string, DownloadItem>;
|
||||||
downloads: DownloadedItems;
|
downloads: DownloadedItems;
|
||||||
|
|
||||||
willDownloadURLs: Map<string, {filePath: string; bookmark?: string}>;
|
willDownloadURLs: Map<string, {filePath: string; bookmark?: string}>;
|
||||||
bookmarks: Map<string, {originalPath: string; bookmark: string}>;
|
bookmarks: Map<string, {originalPath: string; bookmark: string}>;
|
||||||
|
|
||||||
|
@ -172,15 +169,20 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
|
|
||||||
checkForDeletedFiles = () => {
|
checkForDeletedFiles = () => {
|
||||||
log.debug('DownloadsManager.checkForDeletedFiles');
|
log.debug('DownloadsManager.checkForDeletedFiles');
|
||||||
|
|
||||||
const downloads = this.downloads;
|
const downloads = this.downloads;
|
||||||
let modified = false;
|
let modified = false;
|
||||||
|
|
||||||
for (const fileId in downloads) {
|
for (const fileId in downloads) {
|
||||||
if (fileId === APP_UPDATE_KEY) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (Object.prototype.hasOwnProperty.call(downloads, fileId)) {
|
if (Object.prototype.hasOwnProperty.call(downloads, fileId)) {
|
||||||
|
// Remove update if app was updated and restarted
|
||||||
|
if (fileId === APP_UPDATE_KEY) {
|
||||||
|
if (appVersionManager.lastAppVersion === downloads[APP_UPDATE_KEY].filename) {
|
||||||
|
delete downloads[APP_UPDATE_KEY];
|
||||||
|
modified = true;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
const file = downloads[fileId];
|
const file = downloads[fileId];
|
||||||
if (file.state === 'completed') {
|
if (file.state === 'completed') {
|
||||||
if (!file.location || !fs.existsSync(file.location)) {
|
if (!file.location || !fs.existsSync(file.location)) {
|
||||||
|
@ -333,7 +335,6 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
|
|
||||||
openDownloadsDropdown = () => {
|
openDownloadsDropdown = () => {
|
||||||
log.debug('DownloadsManager.openDownloadsDropdown');
|
log.debug('DownloadsManager.openDownloadsDropdown');
|
||||||
|
|
||||||
this.open = true;
|
this.open = true;
|
||||||
ipcMain.emit(OPEN_DOWNLOADS_DROPDOWN);
|
ipcMain.emit(OPEN_DOWNLOADS_DROPDOWN);
|
||||||
WindowManager.sendToRenderer(HIDE_DOWNLOADS_DROPDOWN_BUTTON_BADGE);
|
WindowManager.sendToRenderer(HIDE_DOWNLOADS_DROPDOWN_BUTTON_BADGE);
|
||||||
|
@ -343,10 +344,10 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
return Boolean(this.downloads[APP_UPDATE_KEY]?.type === DownloadItemTypeEnum.UPDATE);
|
return Boolean(this.downloads[APP_UPDATE_KEY]?.type === DownloadItemTypeEnum.UPDATE);
|
||||||
};
|
};
|
||||||
|
|
||||||
removeUpdateBeforeRestart = async () => {
|
removeUpdateBeforeRestart = (): void => {
|
||||||
const downloads = this.downloads;
|
const downloads = this.downloads;
|
||||||
delete downloads[APP_UPDATE_KEY];
|
delete downloads[APP_UPDATE_KEY];
|
||||||
await this.saveAll(downloads);
|
this.saveAll(downloads);
|
||||||
};
|
};
|
||||||
|
|
||||||
private markFileAsDeleted = (item: DownloadedItem) => {
|
private markFileAsDeleted = (item: DownloadedItem) => {
|
||||||
|
@ -363,18 +364,17 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private saveAll = async (downloads: DownloadedItems) => {
|
private saveAll = (downloads: DownloadedItems): void => {
|
||||||
log.debug('DownloadsManager.saveAll');
|
log.debug('DownloadsManager.saveAll');
|
||||||
|
|
||||||
this.downloads = downloads;
|
this.downloads = downloads;
|
||||||
await this.setJson(downloads);
|
this.setJson(downloads);
|
||||||
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN, true, this.downloads);
|
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN, true, this.downloads);
|
||||||
WindowManager?.sendToRenderer(UPDATE_DOWNLOADS_DROPDOWN, this.downloads);
|
WindowManager?.sendToRenderer(UPDATE_DOWNLOADS_DROPDOWN, this.downloads);
|
||||||
};
|
};
|
||||||
|
|
||||||
private save = (key: string, item: DownloadedItem) => {
|
private save = (key: string, item: DownloadedItem) => {
|
||||||
log.debug('DownloadsManager.save');
|
log.debug('DownloadsManager.save');
|
||||||
|
|
||||||
this.downloads[key] = item;
|
this.downloads[key] = item;
|
||||||
this.setValue(key, item);
|
this.setValue(key, item);
|
||||||
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN, true, this.downloads);
|
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN, true, this.downloads);
|
||||||
|
@ -395,7 +395,6 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
*/
|
*/
|
||||||
private shouldShowSaveDialog = (item: DownloadItem, downloadLocation?: string) => {
|
private shouldShowSaveDialog = (item: DownloadItem, downloadLocation?: string) => {
|
||||||
log.debug('DownloadsManager.shouldShowSaveDialog', {downloadLocation});
|
log.debug('DownloadsManager.shouldShowSaveDialog', {downloadLocation});
|
||||||
|
|
||||||
return !item.hasUserGesture() || !downloadLocation;
|
return !item.hasUserGesture() || !downloadLocation;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -406,7 +405,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
|
|
||||||
return dialog.showSaveDialog({
|
return dialog.showSaveDialog({
|
||||||
title: filename,
|
title: filename,
|
||||||
defaultPath: filename,
|
defaultPath: Config.downloadLocation ? path.join(Config.downloadLocation, filename) : filename,
|
||||||
filters,
|
filters,
|
||||||
securityScopedBookmarks: true,
|
securityScopedBookmarks: true,
|
||||||
});
|
});
|
||||||
|
@ -414,11 +413,9 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
|
|
||||||
private closeDownloadsDropdown = () => {
|
private closeDownloadsDropdown = () => {
|
||||||
log.debug('DownloadsManager.closeDownloadsDropdown');
|
log.debug('DownloadsManager.closeDownloadsDropdown');
|
||||||
|
|
||||||
this.open = false;
|
this.open = false;
|
||||||
ipcMain.emit(CLOSE_DOWNLOADS_DROPDOWN);
|
ipcMain.emit(CLOSE_DOWNLOADS_DROPDOWN);
|
||||||
ipcMain.emit(CLOSE_DOWNLOADS_DROPDOWN_MENU);
|
ipcMain.emit(CLOSE_DOWNLOADS_DROPDOWN_MENU);
|
||||||
|
|
||||||
this.clearAutoCloseTimeout();
|
this.clearAutoCloseTimeout();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -432,7 +429,6 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
private upsertFileToDownloads = (item: DownloadItem, state: DownloadItemState, overridePath?: string) => {
|
private upsertFileToDownloads = (item: DownloadItem, state: DownloadItemState, overridePath?: string) => {
|
||||||
const fileId = this.getFileId(item);
|
const fileId = this.getFileId(item);
|
||||||
log.debug('DownloadsManager.upsertFileToDownloads', {fileId});
|
log.debug('DownloadsManager.upsertFileToDownloads', {fileId});
|
||||||
|
|
||||||
const formattedItem = this.formatDownloadItem(item, state, overridePath);
|
const formattedItem = this.formatDownloadItem(item, state, overridePath);
|
||||||
this.save(fileId, formattedItem);
|
this.save(fileId, formattedItem);
|
||||||
this.checkIfMaxFilesReached();
|
this.checkIfMaxFilesReached();
|
||||||
|
@ -442,7 +438,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
const downloads = this.downloads;
|
const downloads = this.downloads;
|
||||||
if (Object.keys(downloads).length > DOWNLOADS_DROPDOWN_MAX_ITEMS) {
|
if (Object.keys(downloads).length > DOWNLOADS_DROPDOWN_MAX_ITEMS) {
|
||||||
const oldestFileId = Object.keys(downloads).reduce((prev, curr) => {
|
const oldestFileId = Object.keys(downloads).reduce((prev, curr) => {
|
||||||
return downloads[prev].addedAt > downloads[curr].addedAt ? curr : prev;
|
return downloads[prev]?.addedAt > downloads[curr]?.addedAt ? curr : prev;
|
||||||
});
|
});
|
||||||
delete downloads[oldestFileId];
|
delete downloads[oldestFileId];
|
||||||
this.saveAll(downloads);
|
this.saveAll(downloads);
|
||||||
|
@ -501,7 +497,6 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.upsertFileToDownloads(item, state, bookmark?.originalPath);
|
this.upsertFileToDownloads(item, state, bookmark?.originalPath);
|
||||||
|
|
||||||
this.fileSizes.delete(item.getFilename());
|
this.fileSizes.delete(item.getFilename());
|
||||||
this.progressingItems.delete(this.getFileId(item));
|
this.progressingItems.delete(this.getFileId(item));
|
||||||
this.shouldAutoClose();
|
this.shouldAutoClose();
|
||||||
|
@ -513,31 +508,28 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
*/
|
*/
|
||||||
private onUpdateAvailable = (event: Event, version = 'unknown') => {
|
private onUpdateAvailable = (event: Event, version = 'unknown') => {
|
||||||
this.save(APP_UPDATE_KEY, {
|
this.save(APP_UPDATE_KEY, {
|
||||||
type: DownloadItemTypeEnum.UPDATE,
|
...UPDATE_DOWNLOAD_ITEM,
|
||||||
filename: version,
|
filename: version,
|
||||||
state: 'available',
|
state: 'available',
|
||||||
progress: 0,
|
|
||||||
location: '',
|
|
||||||
mimeType: null,
|
|
||||||
addedAt: 0,
|
|
||||||
receivedBytes: 0,
|
|
||||||
totalBytes: 0,
|
|
||||||
});
|
});
|
||||||
this.openDownloadsDropdown();
|
this.openDownloadsDropdown();
|
||||||
};
|
};
|
||||||
private onUpdateDownloaded = (event: Event, version = 'unknown') => {
|
private onUpdateDownloaded = (event: Event, info: UpdateInfo) => {
|
||||||
|
log.debug('DownloadsManager.onUpdateDownloaded', {info});
|
||||||
|
|
||||||
|
const {version} = info;
|
||||||
const update = this.downloads[APP_UPDATE_KEY];
|
const update = this.downloads[APP_UPDATE_KEY];
|
||||||
update.state = 'completed';
|
update.state = 'completed';
|
||||||
update.progress = 100;
|
update.progress = 100;
|
||||||
update.filename = version;
|
update.filename = version;
|
||||||
|
|
||||||
this.save(APP_UPDATE_KEY, update);
|
this.save(APP_UPDATE_KEY, update);
|
||||||
this.openDownloadsDropdown();
|
this.openDownloadsDropdown();
|
||||||
};
|
};
|
||||||
private onUpdateProgress = (event: Event, progress: ProgressInfo) => {
|
private onUpdateProgress = (event: Event, progress: ProgressInfo) => {
|
||||||
log.debug('DownloadsManager.onUpdateProgress', {progress});
|
log.debug('DownloadsManager.onUpdateProgress', {progress});
|
||||||
const {total, transferred, percent} = progress;
|
const {total, transferred, percent} = progress;
|
||||||
|
const update = this.downloads[APP_UPDATE_KEY] || {...UPDATE_DOWNLOAD_ITEM};
|
||||||
const update = this.downloads[APP_UPDATE_KEY];
|
|
||||||
if (typeof update.addedAt !== 'number' || update.addedAt === 0) {
|
if (typeof update.addedAt !== 'number' || update.addedAt === 0) {
|
||||||
update.addedAt = Date.now();
|
update.addedAt = Date.now();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ type OwnProps = {
|
||||||
const FileSizeAndStatus = ({item}: OwnProps) => {
|
const FileSizeAndStatus = ({item}: OwnProps) => {
|
||||||
const translate = useIntl();
|
const translate = useIntl();
|
||||||
|
|
||||||
const {totalBytes, receivedBytes, addedAt} = item;
|
const {totalBytes, receivedBytes, addedAt} = item || {};
|
||||||
|
|
||||||
const getRemainingTime = useCallback(() => {
|
const getRemainingTime = useCallback(() => {
|
||||||
const elapsedMs = Date.now() - addedAt;
|
const elapsedMs = Date.now() - addedAt;
|
||||||
|
|
|
@ -58,7 +58,7 @@ class DownloadsDropdown extends React.PureComponent<Record<string, never>, State
|
||||||
} else if (b.type === 'update') {
|
} else if (b.type === 'update') {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return b.addedAt - a.addedAt;
|
return b?.addedAt - a?.addedAt;
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
downloads: newDownloads,
|
downloads: newDownloads,
|
||||||
|
|
Loading…
Reference in a new issue