diff --git a/docs/API-Reference/features/TaskManager.md b/docs/API-Reference/features/TaskManager.md
index fdb774bd3c..d07490a63a 100644
--- a/docs/API-Reference/features/TaskManager.md
+++ b/docs/API-Reference/features/TaskManager.md
@@ -87,7 +87,7 @@ Methods for managing the task's state and UI representation in the TaskManager.
| getProgressPercent | function | Returns the task's current progress percentage. |
| setFailed | function | Marks the task as failed. |
| isFailed | function | Returns true if the task is marked as failed. |
-| setSucceded | function | Marks the task as succeeded. |
+| setSucceeded | function | Marks the task as succeeded. |
| isSucceded | function | Returns true if the task is marked as succeeded. |
| showStopIcon | function | Shows the stop icon with an optional tooltip message. |
| hideStopIcon | function | Hides the stop icon. |
diff --git a/src/extensions/default/PHPSupport/ServerInstaller.js b/src/extensions/default/PHPSupport/ServerInstaller.js
index 83f20f001d..1414d1f707 100644
--- a/src/extensions/default/PHPSupport/ServerInstaller.js
+++ b/src/extensions/default/PHPSupport/ServerInstaller.js
@@ -241,7 +241,7 @@ define(function (require, exports, module) {
}
task.setProgressPercent(100);
task.setMessage(Strings.PHP_INSTALL_DONE);
- task.setSucceded(); // (sic - TaskManager's exported name)
+ task.setSucceeded();
setTimeout(task.close, 4000);
Metrics.countEvent("lsp", "phpInst", upgrading ? "upOk" : "ok");
return { entryPath: getEntryPlatformPath(), upgraded: upgrading };
diff --git a/src/extensions/default/PythonSupport/ServerInstaller.js b/src/extensions/default/PythonSupport/ServerInstaller.js
index 792ec6bfec..1c603a2edf 100644
--- a/src/extensions/default/PythonSupport/ServerInstaller.js
+++ b/src/extensions/default/PythonSupport/ServerInstaller.js
@@ -339,7 +339,7 @@ define(function (require, exports, module) {
currentUnit = null;
task.setProgressPercent(100);
task.setMessage(Strings.PYTHON_INSTALL_DONE);
- task.setSucceded(); // (sic - TaskManager's exported name)
+ task.setSucceeded();
setTimeout(task.close, 4000);
Metrics.countEvent("lsp", "pyInst", upgrading ? "upOk" : "ok");
return { binaryPath: getBinaryPlatformPath(), upgraded: upgrading };
diff --git a/src/extensionsIntegrated/MigrateAssist/constants.js b/src/extensionsIntegrated/MigrateAssist/constants.js
index ff41ed689c..1db7f0e090 100644
--- a/src/extensionsIntegrated/MigrateAssist/constants.js
+++ b/src/extensionsIntegrated/MigrateAssist/constants.js
@@ -46,16 +46,16 @@ define(function (require, exports, module) {
const NEW_ORIGIN = "https://web.phcode.dev";
/**
- * Human readable form of the above, used inside translated sentences via StringUtils.format.
+ * Human readable form of the new origin, used inside translated sentences via StringUtils.format.
+ * The legacy equivalent is derived from the origin instead, see getLegacyDomainName.
*/
- const LEGACY_DOMAIN_NAME = "phcode.dev";
const NEW_DOMAIN_NAME = "web.phcode.dev";
/**
* The day the legacy origin stops serving. Month is 0 based, so 8 is September.
* @type {number}
*/
- const SUNSET_DATE = Date.UTC(2026, 8, 1);
+ const SUNSET_DATE = Date.UTC(2026, 8, 10);
/**
* Android/ChromeOS Trusted Web Activity that wraps the legacy origin. Users launched from this
@@ -135,6 +135,16 @@ define(function (require, exports, module) {
return !Phoenix.isNativeApp && location.origin === getNewOrigin();
}
+ /**
+ * Hostname of the origin being retired, for use inside user facing sentences. Derived from the
+ * origin rather than written out separately so the two can never disagree, which matters while
+ * the legacy origin still points at staging.
+ * @return {string}
+ */
+ function getLegacyDomainName() {
+ return new URL(getLegacyOrigin()).hostname;
+ }
+
/**
* URL of the helper page on the legacy origin.
*
@@ -189,13 +199,13 @@ define(function (require, exports, module) {
return (typeof now === "number" ? now : Date.now()) >= SUNSET_DATE;
}
- exports.LEGACY_DOMAIN_NAME = LEGACY_DOMAIN_NAME;
exports.NEW_DOMAIN_NAME = NEW_DOMAIN_NAME;
exports.SUNSET_DATE = SUNSET_DATE;
exports.TWA_STORE_URL = TWA_STORE_URL;
exports.MIGRATION_DONE_KEY = MIGRATION_DONE_KEY;
exports.getLegacyOrigin = getLegacyOrigin;
exports.getMigrateAssistURL = getMigrateAssistURL;
+ exports.getLegacyDomainName = getLegacyDomainName;
exports.getNewOrigin = getNewOrigin;
exports.isLegacyOrigin = isLegacyOrigin;
exports.isNewOrigin = isNewOrigin;
diff --git a/src/extensionsIntegrated/MigrateAssist/html/migrate-progress.html b/src/extensionsIntegrated/MigrateAssist/html/migrate-progress.html
deleted file mode 100644
index 1b3f051cc2..0000000000
--- a/src/extensionsIntegrated/MigrateAssist/html/migrate-progress.html
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
{{introMessage}}
-
-
{{Strings.PLEASE_WAIT}}
-
-
-
-
-
diff --git a/src/extensionsIntegrated/MigrateAssist/main.js b/src/extensionsIntegrated/MigrateAssist/main.js
index 7ff9caa436..76969e5363 100644
--- a/src/extensionsIntegrated/MigrateAssist/main.js
+++ b/src/extensionsIntegrated/MigrateAssist/main.js
@@ -38,6 +38,7 @@ define(function (require, exports, module) {
Commands = require("command/Commands"),
Menus = require("command/Menus"),
Strings = require("strings"),
+ StringUtils = require("utils/StringUtils"),
Constants = require("./constants"),
SunsetDialog = require("./sunset-dialog"),
Migrator = require("./migrator");
@@ -54,7 +55,9 @@ define(function (require, exports, module) {
// desktop. It is also skipped on Safari/iOS, where the migration is deliberately not
// implemented: offering an action we do not honour would be worse than not offering it.
if (!Phoenix.isTestWindow && Constants.isMigrationSupportedBrowser()) {
- CommandManager.register(Strings.CMD_MIGRATE_DATA, Commands.HELP_MIGRATE_DATA, function () {
+ CommandManager.register(
+ StringUtils.format(Strings.CMD_MIGRATE_DATA, Constants.getLegacyDomainName()),
+ Commands.HELP_MIGRATE_DATA, function () {
Migrator.runManually();
});
// Anchored to About rather than to Check for Updates: the updater only registers its
diff --git a/src/extensionsIntegrated/MigrateAssist/migrator.js b/src/extensionsIntegrated/MigrateAssist/migrator.js
index 61d76dd1b0..b7276146bd 100644
--- a/src/extensionsIntegrated/MigrateAssist/migrator.js
+++ b/src/extensionsIntegrated/MigrateAssist/migrator.js
@@ -35,22 +35,22 @@
define(function (require, exports, module) {
const Dialogs = require("widgets/Dialogs"),
DefaultDialogs = require("widgets/DefaultDialogs"),
- Mustache = require("thirdparty/mustache/mustache"),
Strings = require("strings"),
StringUtils = require("utils/StringUtils"),
Metrics = require("utils/Metrics"),
- ZipUtils = require("utils/ZipUtils"),
+ TaskManager = require("features/TaskManager"),
PreferencesManager = require("preferences/PreferencesManager"),
CommandManager = require("command/CommandManager"),
Commands = require("command/Commands"),
- Constants = require("./constants"),
- progressTemplate = require("text!./html/migrate-progress.html");
+ Constants = require("./constants");
const HANDSHAKE_TIMEOUT_MS = 15000,
- BUNDLE_TIMEOUT_MS = 120000,
+ FILE_TIMEOUT_MS = 60000,
+ CHUNK_SIZE = 8 * 1024 * 1024,
IFRAME_ID = "migrate-assist-frame";
const RESULT_MIGRATED = "migrated",
+ RESULT_DECLINED = "declined",
RESULT_NOTHING = "nothing",
RESULT_UNREACHABLE = "unreachable";
@@ -68,7 +68,7 @@ define(function (require, exports, module) {
iframe.style.display = "none";
let pendingScan = null,
- bundleHandler = null,
+ fileHandler = null,
destroyed = false;
function _onMessage(event) {
@@ -87,8 +87,8 @@ define(function (require, exports, module) {
const resolve = pendingScan;
pendingScan = null;
resolve(data);
- } else if (bundleHandler) {
- bundleHandler(data);
+ } else if (fileHandler) {
+ fileHandler(data);
}
}
@@ -126,100 +126,158 @@ define(function (require, exports, module) {
}
/**
- * Requests one bundle and reassembles its chunks into a single ArrayBuffer.
+ * Requests one file, reassembling it from chunks if it is larger than the chunk size.
*/
- function fetchBundle(id) {
+ function fetchFile(path) {
return new Promise((resolve, reject) => {
- const chunks = [];
- let expected = -1,
- received = 0;
+ const parts = [];
+ let received = 0;
const timer = setTimeout(() => {
- bundleHandler = null;
- reject(new Error("timed out receiving " + id));
- }, BUNDLE_TIMEOUT_MS);
+ fileHandler = null;
+ reject(new Error("timed out receiving " + path));
+ }, FILE_TIMEOUT_MS);
- bundleHandler = function (data) {
- if (data.id !== id) {
+ function requestFrom(offset) {
+ iframe.contentWindow.postMessage(
+ { type: "MIGRATE_READ", path: path, offset: offset, length: CHUNK_SIZE },
+ legacyOrigin);
+ }
+
+ fileHandler = function (data) {
+ if (data.path !== path) {
return;
}
if (data.type === "MIGRATE_ERROR") {
clearTimeout(timer);
- bundleHandler = null;
+ fileHandler = null;
reject(new Error(data.message));
- } else if (data.type === "MIGRATE_BUNDLE_META") {
- expected = data.chunkCount;
- } else if (data.type === "MIGRATE_CHUNK") {
- chunks[data.index] = data.chunk;
- received = received + 1;
- if (data.last || (expected > 0 && received === expected)) {
- clearTimeout(timer);
- bundleHandler = null;
- const total = chunks.reduce((sum, c) => sum + c.byteLength, 0);
- const merged = new Uint8Array(total);
- let offset = 0;
- for (const chunk of chunks) {
- merged.set(new Uint8Array(chunk), offset);
- offset = offset + chunk.byteLength;
- }
- resolve(merged.buffer);
- }
+ return;
+ }
+ if (data.type !== "MIGRATE_DATA") {
+ return;
}
+ parts.push(new Uint8Array(data.chunk));
+ received = received + data.chunk.byteLength;
+ if (!data.eof) {
+ requestFrom(received);
+ return;
+ }
+ clearTimeout(timer);
+ fileHandler = null;
+ if (parts.length === 1) {
+ resolve(parts[0]);
+ return;
+ }
+ const merged = new Uint8Array(received);
+ let offset = 0;
+ for (const part of parts) {
+ merged.set(part, offset);
+ offset = offset + part.byteLength;
+ }
+ resolve(merged);
};
- iframe.contentWindow.postMessage({ type: "MIGRATE_BUNDLE", id: id }, legacyOrigin);
+ requestFrom(0);
});
}
- return { scan, fetchBundle, destroy };
+ return { scan, fetchFile, destroy };
}
- function _showProgressDialog(bundleCount) {
- const dialog = Dialogs.showModalDialogUsingTemplate(
- Mustache.render(progressTemplate, {
- Strings: Strings,
- introMessage: StringUtils.format(Strings.MIGRATE_PROGRESS_INTRO,
- Constants.LEGACY_DOMAIN_NAME)
- }),
- false // no auto dismiss, the transfer must not be interrupted half way
- );
- const $dlg = dialog.getElement();
- let bundlesDone = 0;
-
+ /**
+ * Progress lives in the status bar rather than a modal. The transfer is dominated by IndexedDB
+ * writes at roughly 10ms per file, so a few thousand files is minutes long, and blocking the
+ * whole editor behind an undismissable dialog for that is not acceptable. The user is told once
+ * up front, works normally while it runs, and gets a dialog again only when it is done.
+ */
+ function _startProgressTask(totalFiles) {
+ const task = TaskManager.addNewTask(
+ Strings.MIGRATE_PROGRESS_TITLE,
+ StringUtils.format(Strings.MIGRATE_PROGRESS_STATUS, 0, totalFiles),
+ ``);
return {
- dialog: dialog,
- setWaiting: function (index, name) {
- // The remote side is zipping. Nothing to count yet, so pulse rather than sit at 0%.
- $dlg.find(".migrate-assist-bar").addClass("migrate-assist-bar-indeterminate");
- $dlg.find(".migrate-assist-status")
- .text(StringUtils.format(Strings.MIGRATE_PROGRESS_PREPARING, name, index + 1, bundleCount));
+ update: function (done) {
+ task.setProgressPercent(Math.round((done / totalFiles) * 100));
+ task.setMessage(StringUtils.format(Strings.MIGRATE_PROGRESS_STATUS, done, totalFiles));
},
- setBundleProgress: function (index, name, doneFiles, totalFiles) {
- $dlg.find(".migrate-assist-bar").removeClass("migrate-assist-bar-indeterminate");
- bundlesDone = index;
- const withinBundle = totalFiles ? (doneFiles / totalFiles) : 0;
- const overall = Math.min(100, Math.round(((bundlesDone + withinBundle) / bundleCount) * 100));
- $dlg.find(".migrate-assist-bar").css("width", `${overall}%`);
- $dlg.find(".migrate-assist-status")
- .text(StringUtils.format(Strings.MIGRATE_PROGRESS_STATUS, name, index + 1, bundleCount));
+ succeed: function (done) {
+ task.setProgressPercent(100);
+ task.setMessage(StringUtils.format(Strings.MIGRATE_PROGRESS_STATUS, done, totalFiles));
+ task.setSucceeded();
+ task.close();
},
- finish: function (summary) {
- $dlg.find(".migrate-assist-bar")
- .removeClass("migrate-assist-bar-indeterminate")
- .css("width", "100%");
- $dlg.find(".migrate-assist-intro").text(Strings.MIGRATE_DONE_TITLE);
- $dlg.find(".migrate-assist-status").text(summary.message);
- if (summary.detail) {
- $dlg.find(".migrate-assist-detail").removeClass("forced-hidden").text(summary.detail);
- }
- $dlg.find(".migrate-assist-reload").removeClass("forced-hidden").on("click", function () {
- CommandManager.execute(Commands.APP_RELOAD);
- });
- $dlg.find(".migrate-assist-close").removeClass("forced-hidden").on("click", function () {
- dialog.close();
- });
+ fail: function () {
+ task.setFailed();
+ task.close();
}
};
}
+ /**
+ * Shown once, before anything is copied, so the user knows why their machine is busy.
+ */
+ function _confirmStart(fileCount) {
+ return Dialogs.showModalDialog(
+ DefaultDialogs.DIALOG_ID_INFO,
+ Strings.MIGRATE_PROGRESS_TITLE,
+ StringUtils.format(Strings.MIGRATE_START_MESSAGE, fileCount, Constants.getLegacyDomainName()),
+ [
+ {
+ className: Dialogs.DIALOG_BTN_CLASS_NORMAL,
+ id: Dialogs.DIALOG_BTN_CANCEL,
+ text: Strings.CANCEL
+ },
+ {
+ className: Dialogs.DIALOG_BTN_CLASS_PRIMARY,
+ id: Dialogs.DIALOG_BTN_OK,
+ text: Strings.MIGRATE_START_CONFIRM
+ }
+ ]
+ ).getPromise();
+ }
+
+ function _showCompletion(migratedFiles, failed) {
+ const message = failed.length
+ ? StringUtils.format(Strings.MIGRATE_DONE_MESSAGE, migratedFiles) + "
"
+ + StringUtils.format(Strings.MIGRATE_DONE_PARTIAL, failed.length)
+ : StringUtils.format(Strings.MIGRATE_DONE_MESSAGE, migratedFiles);
+ Dialogs.showModalDialog(
+ DefaultDialogs.DIALOG_ID_INFO,
+ Strings.MIGRATE_DONE_TITLE,
+ message,
+ [
+ {
+ className: Dialogs.DIALOG_BTN_CLASS_NORMAL,
+ id: Dialogs.DIALOG_BTN_CANCEL,
+ text: Strings.MIGRATE_RELOAD_LATER
+ },
+ {
+ className: Dialogs.DIALOG_BTN_CLASS_PRIMARY,
+ id: Dialogs.DIALOG_BTN_OK,
+ text: Strings.MIGRATE_RELOAD_NOW
+ }
+ ]
+ ).done(function (buttonId) {
+ if (buttonId === Dialogs.DIALOG_BTN_OK) {
+ CommandManager.execute(Commands.APP_RELOAD);
+ }
+ });
+ }
+
+ /**
+ * Filer needs the parent directory to exist before a write, and mkdirs is recursive.
+ */
+ function _ensureParentDir(filePath) {
+ return new Promise((resolve, reject) => {
+ window.fs.mkdirs(window.path.dirname(filePath), 0o755, true, (err) => {
+ if (err && err.code !== "EEXIST") {
+ reject(err);
+ return;
+ }
+ resolve();
+ });
+ });
+ }
+
async function _applyPreferences(prefsText) {
// Flush first. The in memory user scope is authoritative, so overwriting the file underneath
// it would just get clobbered by the next save.
@@ -250,35 +308,39 @@ define(function (require, exports, module) {
let progress = null;
try {
const scan = await bridge.scan();
- if (!scan.hasData || !scan.bundles.length) {
+ if (!scan.hasData || !scan.files.length) {
Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM, "migrateAssist",
manual ? "manualNothing" : "autoNothing");
return RESULT_NOTHING;
}
+ // Asked once, before anything is copied. After this the user is left alone.
+ const choice = await _confirmStart(scan.files.length);
+ if (choice !== Dialogs.DIALOG_BTN_OK) {
+ Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM, "migrateAssist", "declined");
+ return RESULT_DECLINED;
+ }
+
Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM, "migrateAssist",
manual ? "manualStart" : "autoStart");
- progress = _showProgressDialog(scan.bundles.length);
+ progress = _startProgressTask(scan.files.length);
const failed = [];
let migratedFiles = 0;
- for (let i = 0; i < scan.bundles.length; i++) {
- const bundle = scan.bundles[i];
- const name = bundle.dest.substring(bundle.dest.lastIndexOf("/") + 1);
- progress.setWaiting(i, name);
+ for (const file of scan.files) {
try {
- const buffer = await bridge.fetchBundle(bundle.id);
- await ZipUtils.unzipBinDataToLocation(buffer, bundle.dest, false,
- function (doneCount, totalCount) {
- progress.setBundleProgress(i, name, doneCount, totalCount);
- return true; // must be explicit, see unzipBinDataToLocation
- });
- migratedFiles = migratedFiles + bundle.fileCount;
+ const bytes = await bridge.fetchFile(file.path);
+ // Paths are identical on both origins, so the destination is the source path.
+ await _ensureParentDir(file.path);
+ await Phoenix.VFS.writeFileAsync(file.path, window.Filer.Buffer.from(bytes),
+ window.fs.BYTE_ARRAY_ENCODING);
+ migratedFiles = migratedFiles + 1;
} catch (err) {
- // One bad folder should not cost the user everything else.
- console.error("MigrateAssist: bundle failed", bundle.id, err);
- failed.push(name);
+ // One unreadable file should not cost the user everything else.
+ console.error("MigrateAssist: could not copy", file.path, err);
+ failed.push(file.path);
}
+ progress.update(migratedFiles + failed.length);
}
if (scan.prefs) {
@@ -299,19 +361,15 @@ define(function (require, exports, module) {
Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM, "migrateAssist",
failed.length ? "completedWithErrors" : "completed");
- progress.finish({
- message: StringUtils.format(Strings.MIGRATE_DONE_MESSAGE, migratedFiles),
- detail: failed.length
- ? StringUtils.format(Strings.MIGRATE_DONE_PARTIAL, failed.join(", "))
- : null
- });
+ progress.succeed(migratedFiles);
+ _showCompletion(migratedFiles, failed);
return RESULT_MIGRATED;
} catch (err) {
console.error("MigrateAssist: migration could not run", err);
Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM, "migrateAssist",
manual ? "manualUnreachable" : "autoUnreachable");
if (progress) {
- progress.dialog.close();
+ progress.fail();
}
return RESULT_UNREACHABLE;
} finally {
@@ -348,14 +406,17 @@ define(function (require, exports, module) {
*/
async function runManually() {
const result = await run(true);
+ if (result === RESULT_DECLINED) {
+ return; // the user said no, they do not need to be told what they just chose
+ }
if (result === RESULT_NOTHING) {
Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_INFO,
Strings.MIGRATE_NOTHING_TITLE,
- StringUtils.format(Strings.MIGRATE_NOTHING_MESSAGE, Constants.LEGACY_DOMAIN_NAME));
+ StringUtils.format(Strings.MIGRATE_NOTHING_MESSAGE, Constants.getLegacyDomainName()));
} else if (result === RESULT_UNREACHABLE) {
Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_ERROR,
Strings.MIGRATE_UNREACHABLE_TITLE,
- StringUtils.format(Strings.MIGRATE_UNREACHABLE_MESSAGE, Constants.LEGACY_DOMAIN_NAME));
+ StringUtils.format(Strings.MIGRATE_UNREACHABLE_MESSAGE, Constants.getLegacyDomainName()));
}
}
diff --git a/src/extensionsIntegrated/MigrateAssist/sunset-dialog.js b/src/extensionsIntegrated/MigrateAssist/sunset-dialog.js
index 85ec1ff579..7baf156d7a 100644
--- a/src/extensionsIntegrated/MigrateAssist/sunset-dialog.js
+++ b/src/extensionsIntegrated/MigrateAssist/sunset-dialog.js
@@ -51,13 +51,13 @@ define(function (require, exports, module) {
const paragraphs = [];
paragraphs.push(StringUtils.format(Strings.MIGRATE_MOVING_MESSAGE,
- Constants.LEGACY_DOMAIN_NAME, Constants.NEW_DOMAIN_NAME));
+ Constants.getLegacyDomainName(), Constants.NEW_DOMAIN_NAME));
if (!Constants.isPastSunset()) {
const days = Constants.daysToSunset();
paragraphs.push(StringUtils.format(
days === 1 ? Strings.MIGRATE_SUNSET_COUNTDOWN_ONE : Strings.MIGRATE_SUNSET_COUNTDOWN,
- days, Constants.LEGACY_DOMAIN_NAME));
+ days, Constants.getLegacyDomainName()));
}
if (!Constants.isMigrationSupportedBrowser()) {
@@ -112,7 +112,7 @@ define(function (require, exports, module) {
Dialogs.showModalDialog(
DefaultDialogs.DIALOG_ID_INFO,
- StringUtils.format(Strings.MIGRATE_MOVING_TITLE, Constants.LEGACY_DOMAIN_NAME),
+ StringUtils.format(Strings.MIGRATE_MOVING_TITLE, Constants.getLegacyDomainName()),
_buildMessage(),
_buildButtons()
).done(function (buttonId) {
diff --git a/src/extensionsIntegrated/appUpdater/main.js b/src/extensionsIntegrated/appUpdater/main.js
index f2ea906989..fbc45a686a 100644
--- a/src/extensionsIntegrated/appUpdater/main.js
+++ b/src/extensionsIntegrated/appUpdater/main.js
@@ -582,7 +582,7 @@ define(function (require, exports, module) {
} else if(data === UPDATE_STATUS.INSTALLER_DOWNLOADED){
Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'downloaded', Phoenix.platform);
updatePendingRestart = true;
- updateTask.setSucceded();
+ updateTask.setSucceeded();
updateTask.setTitle(Strings.UPDATE_DONE);
updateTask.setMessage(Strings.UPDATE_RESTART_INSTALL);
if(!updateInstalledDialogShown){
diff --git a/src/extensionsIntegrated/appUpdater/update-electron.js b/src/extensionsIntegrated/appUpdater/update-electron.js
index 9629b88d2b..0c57fdf89c 100644
--- a/src/extensionsIntegrated/appUpdater/update-electron.js
+++ b/src/extensionsIntegrated/appUpdater/update-electron.js
@@ -194,7 +194,7 @@ define(function (require, exports, module) {
await window.electronAPI.setUpdateScheduled(true);
showOrHideUpdateIcon();
Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'scheduled', Phoenix.platform);
- updateTask.setSucceded();
+ updateTask.setSucceeded();
updateTask.setTitle(Strings.UPDATE_DONE);
updateTask.setMessage(Strings.UPDATE_RESTART_INSTALL);
NotificationUI.createToastFromTemplate(Strings.UPDATE_READY_RESTART_TITLE,
@@ -406,7 +406,7 @@ define(function (require, exports, module) {
Strings.UPDATE_READY_RESTART_INSTALL_MESSAGE);
}
});
- updateTask.setSucceded();
+ updateTask.setSucceeded();
Phoenix.app.registerQuitTimeAppUpdateHandler(quitTimeAppUpdateHandler);
console.log("Update was scheduled in another window, registering quit handler");
}
diff --git a/src/features/TaskManager.js b/src/features/TaskManager.js
index c1450e2c0c..31b558f8d5 100644
--- a/src/features/TaskManager.js
+++ b/src/features/TaskManager.js
@@ -318,7 +318,7 @@ define(function (require, exports, module) {
* @property {function(): number} getProgressPercent - Returns the task's current progress percentage.
* @property {function(): void} setFailed - Marks the task as failed.
* @property {function(): boolean} isFailed - Returns true if the task is marked as failed.
- * @property {function(): void} setSucceded - Marks the task as succeeded.
+ * @property {function(): void} setSucceeded - Marks the task as succeeded.
* @property {function(): boolean} isSucceded - Returns true if the task is marked as succeeded.
* @property {function(string): void} showStopIcon - Shows the stop icon with an optional tooltip message.
* @property {function(): void} hideStopIcon - Hides the stop icon.
@@ -521,7 +521,7 @@ define(function (require, exports, module) {
task.getTitle = getTitle;
task.setMessage = setMessage;
task.getMessage = getMessage;
- task.setSucceded = setSucceeded;
+ task.setSucceeded = setSucceeded;
task.isSucceeded = isSucceeded;
task.setFailed = setFailed;
task.isFailed = isFailed;
diff --git a/src/migrateAssist.html b/src/migrateAssist.html
index be36c09ca0..ffb4bb9b24 100644
--- a/src/migrateAssist.html
+++ b/src/migrateAssist.html
@@ -38,9 +38,14 @@
It deliberately does not boot Phoenix. virtualfs.js is a self contained IIFE that gives us
window.fs over the same Filer IndexedDB, which is all we need.
+
+ Files are streamed one at a time rather than zipped per folder. IndexedDB costs roughly 10ms per
+ file whatever we do, so an intermediate zip only adds a read/encode/decode pass on top of that
+ floor, and measured on 300 files it produced an archive slightly LARGER than the input because
+ JSZip stores uncompressed. Streaming also gives the receiver an exact per file progress count and
+ keeps peak memory at one file instead of one whole folder.
-->
-