Repository navigation
Expand file tree
/
Copy pathmigrator.js
More file actions
1 lines (1 loc) · 11.6 KB
/
Copy pathmigrator.js
File metadata and controls
1 lines (1 loc) · 11.6 KB
1
define(function(require,exports,module){const Dialogs=require("widgets/Dialogs"),DefaultDialogs=require("widgets/DefaultDialogs"),NotificationUI=require("widgets/NotificationUI"),Strings=require("strings"),StringUtils=require("utils/StringUtils"),Metrics=require("utils/Metrics"),TaskManager=require("features/TaskManager"),PreferencesManager=require("preferences/PreferencesManager"),CommandManager=require("command/CommandManager"),FileUtils=require("file/FileUtils"),Commands=require("command/Commands"),Constants=require("./constants"),HANDSHAKE_TIMEOUT_MS=15e3,FILE_TIMEOUT_MS=6e4,CHUNK_SIZE=8388608,IFRAME_ID="migrate-assist-frame",RESULT_MIGRATED="migrated",RESULT_INTERRUPTED="interrupted",RESULT_NOTHING="nothing",RESULT_UNREACHABLE="unreachable";let migrationRunning=!1;function _createBridge(){const legacyOrigin=Constants.getLegacyOrigin(),iframe=document.createElement("iframe");iframe.id=IFRAME_ID,iframe.setAttribute("title","data migration helper"),iframe.style.display="none";let pendingScan=null,fileHandler=null,destroyed=!1;function _onMessage(event){if(event.origin!==legacyOrigin||event.source!==iframe.contentWindow)return;const data=event.data;if(data&&"object"==typeof data)if("MIGRATE_READY"===data.type&&pendingScan)iframe.contentWindow.postMessage({type:"MIGRATE_SCAN"},legacyOrigin);else if("MIGRATE_SCAN_RESULT"===data.type&&pendingScan){const resolve=pendingScan;pendingScan=null,resolve(data)}else fileHandler&&fileHandler(data)}function destroy(){destroyed||(destroyed=!0,window.removeEventListener("message",_onMessage),iframe.parentNode&&iframe.parentNode.removeChild(iframe))}function scan(){return new Promise((resolve,reject)=>{const timer=setTimeout(()=>{pendingScan=null,reject(new Error("timed out waiting for "+legacyOrigin))},HANDSHAKE_TIMEOUT_MS);pendingScan=function(result){clearTimeout(timer),resolve(result)},iframe.src=Constants.getMigrateAssistURL()+"?parentOrigin="+encodeURIComponent(location.origin),document.body.appendChild(iframe)})}function fetchFile(path){return new Promise((resolve,reject)=>{const parts=[];let received=0;const timer=setTimeout(()=>{fileHandler=null,reject(new Error("timed out receiving "+path))},FILE_TIMEOUT_MS);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("MIGRATE_ERROR"===data.type)return clearTimeout(timer),fileHandler=null,void reject(new Error(data.message));if("MIGRATE_DATA"!==data.type)return;if(parts.push(new Uint8Array(data.chunk)),received+=data.chunk.byteLength,!data.eof)return void requestFrom(received);if(clearTimeout(timer),fileHandler=null,1===parts.length)return void resolve(parts[0]);const merged=new Uint8Array(received);let offset=0;for(const part of parts)merged.set(part,offset),offset+=part.byteLength;resolve(merged)},requestFrom(0)})}return window.addEventListener("message",_onMessage),{scan:scan,fetchFile:fetchFile,destroy:destroy}}function _startProgressTask(totalFiles){const task=TaskManager.addNewTask(Strings.MIGRATE_PROGRESS_TITLE,StringUtils.format(Strings.MIGRATE_PROGRESS_STATUS,0,totalFiles),'<i class="fa-solid fa-download"></i>');return $(".dropdown-menu:visible").length||task.show(),{update:function(done){task.setProgressPercent(Math.round(done/totalFiles*100)),task.setMessage(StringUtils.format(Strings.MIGRATE_PROGRESS_STATUS,done,totalFiles))},succeed:function(done){task.setProgressPercent(100),task.setMessage(StringUtils.format(Strings.MIGRATE_PROGRESS_STATUS,done,totalFiles)),task.setSucceeded(),task.close()},fail:function(){task.setFailed(),task.close()}}}function _errorDialog(title,message){Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_ERROR,title,message)}function _toast(title,message,style,$extra){const $content=$("<div>").append($("<div>").text(message));return $extra&&$content.append($extra),NotificationUI.createToastFromTemplate(title,$content,{dismissOnClick:!1,toastStyle:style})}function _menuPath(){return`${Strings.HELP_MENU} > `+StringUtils.format(Strings.CMD_MIGRATE_DATA,Constants.getLegacyDomainName())}function _recordCategory(filePath,projects,extensions){const parts=filePath.split("/");filePath.startsWith("/fs/local/")&&parts[3]?projects.add(parts[3]):filePath.startsWith("/fs/app/extensions/")&&parts[5]&&extensions.add(parts[5])}function _showCompletion(summary){const items=[];summary.projects&&items.push(StringUtils.format(Strings.MIGRATE_DONE_PROJECTS,summary.projects)),summary.extensions&&items.push(StringUtils.format(Strings.MIGRATE_DONE_EXTENSIONS,summary.extensions)),summary.settings&&items.push(Strings.MIGRATE_DONE_SETTINGS);let message=StringUtils.format(Strings.MIGRATE_DONE_FROM,Constants.getLegacyDomainName());message=message+"<ul>"+items.map(item=>`<li>${item}</li>`).join("")+"</ul>",summary.failed?message=(message+=StringUtils.format(Strings.MIGRATE_PARTIAL_COUNT,summary.failed,summary.total))+"<br><br>"+(summary.outOfRetries?StringUtils.format(Strings.MIGRATE_INTERRUPTED_FINAL,_menuPath()):Strings.MIGRATE_INTERRUPTED_RETRY):message+=StringUtils.format(Strings.MIGRATE_DONE_TOTAL,summary.files),Dialogs.showModalDialog(summary.failed?DefaultDialogs.DIALOG_ID_ERROR:DefaultDialogs.DIALOG_ID_INFO,summary.failed?Strings.MIGRATE_PARTIAL_TITLE: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){buttonId===Dialogs.DIALOG_BTN_OK&&CommandManager.execute(Commands.APP_RELOAD)})}function _ensureParentDir(filePath){return new Promise((resolve,reject)=>{window.fs.mkdirs(window.path.dirname(filePath),493,!0,err=>{err&&"EEXIST"!==err.code?reject(err):resolve()})})}async function _applyPreferences(prefsText){await PreferencesManager.save();const prefFile=PreferencesManager.getUserPrefFile();await Phoenix.VFS.writeFileAsync(prefFile,prefsText,"utf8"),PreferencesManager.fileChanged(prefFile)}const RECENT_PROJECTS_KEY="STATE_recentProjects",MAX_RECENT_PROJECTS=20;async function _applyRecentProjects(incoming){if(!Array.isArray(incoming))return;const merged=[];for(const rawPath of incoming.concat(PhStore.getItem(RECENT_PROJECTS_KEY)||[])){if(!rawPath)continue;const projectPath=FileUtils.stripTrailingSlash(rawPath);projectPath&&-1===merged.indexOf(projectPath)&&merged.push(projectPath)}const mountDir=Phoenix.VFS.getMountDir(),usable=[];for(const projectPath of merged)projectPath.startsWith(mountDir)||await Phoenix.VFS.existsAsync(projectPath)&&usable.push(projectPath);PhStore.setItem(RECENT_PROJECTS_KEY,usable.slice(0,MAX_RECENT_PROJECTS)),Metrics.valueEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","recentsDropped",merged.length-usable.length)}async function _applyPhStore(phStore){for(const key of Object.keys(phStore||{}))key!==RECENT_PROJECTS_KEY?PhStore.setItem(key,phStore[key]):await _applyRecentProjects(phStore[key])}async function run(manual){if(migrationRunning)return RESULT_NOTHING;migrationRunning=!0;const startedAt=performance.now(),bridge=_createBridge();let progress=null,transferStarted=!1,attemptNumber=0,totalFiles=0,migratedFiles=0;try{const scan=await bridge.scan();if(Metrics.valueEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","scanMs",Math.round(performance.now()-startedAt)),!scan.hasData||!scan.files.length)return Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist",manual?"manualNothing":"autoNothing"),RESULT_NOTHING;Metrics.valueEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","foundFiles",scan.files.length),Metrics.valueEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","foundKB",Math.round((scan.totalBytes||0)/1024)),transferStarted=!0,attemptNumber=(PhStore.getItem(Constants.MIGRATION_ATTEMPTS_KEY)||0)+1,PhStore.setItem(Constants.MIGRATION_ATTEMPTS_KEY,attemptNumber),Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist",manual?"manualStart":"autoStart"),progress=_startProgressTask(scan.files.length);const failed=[];totalFiles=scan.files.length;let migratedBytes=0;const migratedProjects=new Set,migratedExtensions=new Set;for(const file of scan.files){try{const bytes=await bridge.fetchFile(file.path);await _ensureParentDir(file.path),await Phoenix.VFS.writeFileAsync(file.path,window.Filer.Buffer.from(bytes),window.fs.BYTE_ARRAY_ENCODING),migratedFiles+=1,migratedBytes+=bytes.byteLength,_recordCategory(file.path,migratedProjects,migratedExtensions)}catch(err){console.error("MigrateAssist: could not copy",file.path,err),failed.push(file.path)}progress.update(migratedFiles+failed.length)}if(scan.prefs)try{await _applyPreferences(scan.prefs)}catch(err){console.error("MigrateAssist: could not apply preferences",err),failed.push("phcode.json")}await _applyPhStore(scan.phStore);const outOfRetries=attemptNumber>=Constants.MAX_AUTO_ATTEMPTS;failed.length||PhStore.setItem(Constants.MIGRATION_DONE_KEY,{at:Date.now(),files:migratedFiles,failed:0});const elapsedMs=Math.round(performance.now()-startedAt);return Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist",failed.length?"completedWithErrors":"completed"),Metrics.valueEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","migratedFiles",migratedFiles),Metrics.valueEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","migratedKB",Math.round(migratedBytes/1024)),Metrics.valueEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","durationSec",Math.round(elapsedMs/1e3)),Metrics.valueEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","msPerFile",migratedFiles?Math.round(elapsedMs/migratedFiles):0),failed.length&&Metrics.valueEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","failedFiles",failed.length),progress.succeed(migratedFiles),_showCompletion({files:migratedFiles,total:totalFiles,projects:migratedProjects.size,extensions:migratedExtensions.size,settings:!!scan.prefs,failed:failed.length,outOfRetries:outOfRetries}),RESULT_MIGRATED}catch(err){if(console.error("MigrateAssist: migration could not run",err),progress&&progress.fail(),transferStarted){Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist",`interrupted.attempt${attemptNumber}`),Metrics.valueEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","interruptedAfterSec",Math.round((performance.now()-startedAt)/1e3));const outOfRetries=attemptNumber>=Constants.MAX_AUTO_ATTEMPTS;return _errorDialog(Strings.MIGRATE_INTERRUPTED_TITLE,StringUtils.format(Strings.MIGRATE_INTERRUPTED_PROGRESS,migratedFiles,totalFiles,Constants.getLegacyDomainName())+"<br><br>"+(outOfRetries?StringUtils.format(Strings.MIGRATE_INTERRUPTED_FINAL,_menuPath()):Strings.MIGRATE_INTERRUPTED_RETRY)),RESULT_INTERRUPTED}return Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist",manual?"manualUnreachable":"autoUnreachable"),RESULT_UNREACHABLE}finally{migrationRunning=!1,bridge.destroy()}}function runOnBoot(){!Constants.isNewOrigin()||Phoenix.isNativeApp||Phoenix.isTestWindow||Constants.isMigrationSupportedBrowser()&&(PhStore.getItem(Constants.MIGRATION_DONE_KEY)||((PhStore.getItem(Constants.MIGRATION_ATTEMPTS_KEY)||0)>=Constants.MAX_AUTO_ATTEMPTS?Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","skipOutOfAttempts"):(Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","autoProbe"),run(!1))))}async function runManually(){Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM,"migrateAssist","manualInvoked");const result=await run(!0);result!==RESULT_INTERRUPTED&&(result===RESULT_NOTHING?_toast(Strings.MIGRATE_NOTHING_TITLE,StringUtils.format(Strings.MIGRATE_NOTHING_MESSAGE,Constants.getLegacyDomainName()),NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.INFO):result===RESULT_UNREACHABLE&&_errorDialog(Strings.MIGRATE_UNREACHABLE_TITLE,StringUtils.format(Strings.MIGRATE_UNREACHABLE_MESSAGE,Constants.getLegacyDomainName(),_menuPath())))}exports.runOnBoot=runOnBoot,exports.runManually=runManually});