file_operation.html 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. <html>
  2. <head>
  3. <title>File Operation</title>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
  6. <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
  7. <script type="text/javascript" src="../../script/jquery.min.js"></script>
  8. <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
  9. <script type="text/javascript" src="../../script/ao_module.js"></script>
  10. <style>
  11. .banner{
  12. background-color:#4287f5;
  13. height:50px;
  14. padding:12px;
  15. padding-left:20px;
  16. padding-top:16px;
  17. }
  18. #opricon{
  19. position:absolute;
  20. top:0px;
  21. right:0px;
  22. width:80px;
  23. height:80px;
  24. }
  25. .title{
  26. color:white;
  27. font-size:130%;
  28. }
  29. .content{
  30. padding:12px;
  31. }
  32. .info{
  33. margin-top:3px;
  34. white-space: nowrap;
  35. overflow: hidden;
  36. text-overflow: ellipsis;
  37. }
  38. </style>
  39. </head>
  40. <body>
  41. <div class="banner">
  42. <div class="title">Calculating Operation</div>
  43. <img id="opricon" src="img/loading.png" class="ui image"></img>
  44. </div>
  45. <div class="content">
  46. <div class="info">From: <span id="src"></span></div>
  47. <div class="info">To: <span id="dest"></span></div>
  48. <div class="info">Progress: <span id="progress"> <i class="loading spinner icon"></i> Calculating</span></div>
  49. <div class="ui active small progress" style="margin-top:18px;">
  50. <div id="progressbar" class="bar" style="width:100%; background-color:#4287f5;"></div>
  51. </div>
  52. </div>
  53. <div class="ui modal" id="duplicateAction">
  54. <div class="content">
  55. <div class="description" style="padding: 0px !important;">
  56. <p><i class="big exclamation triangle icon"></i> At least one file has the same filename with another existsing file. <b>Which action should be taken?</b></p><br>
  57. </div>
  58. </div>
  59. <div class="actions">
  60. <div class="ui labeled button" onclick="continueProcess('overwrite');">
  61. Overwrite
  62. </div>
  63. <div class="ui labeled button" onclick="continueProcess('skip');">
  64. Skip
  65. </div>
  66. <div class="ui labeled button" style="color: #2fb55c;" onclick="continueProcess('keep');">
  67. Rename & Keep
  68. </div>
  69. </div>
  70. </div>
  71. <script>
  72. /*
  73. ArOZ Online File Operation Listener
  74. Usage: Pass in the following JSON object as hash with encodeURIComponent after JSON stringify
  75. {
  76. opr: {move / copy / zip / unzip / download / zipAndDown / unzipAndOpen},
  77. src: {filelist, allow multiple files},
  78. dest: {filepath},
  79. //Optional paramters
  80. overwriteMode: {skip / overwrite / keep},
  81. callbackWindowID: {floatWindow ID},
  82. callbackFunction: {target Window Function Name as String}
  83. }
  84. **For download opr, it will first buffer into the browser memory.
  85. It is not recommended for files > 2GB (Firefox Limit)
  86. Example callbackFunction: "refreshList()" (string)
  87. */
  88. var operationConfig = null;
  89. var opr = "";
  90. var maxPathDisplayLength = 40;
  91. var legacyMode = !('WebSocket' in window || 'MozWebSocket' in window); //Use AJAX instead of WebSocket if legacy mode is activated
  92. var enterErrorMode = false;
  93. //Initalized floatWindow events
  94. ao_module_setFixedWindowSize();
  95. ao_module_setWindowSize(400,220);
  96. init();
  97. function init(){
  98. console.log("Checking launch parameters...");
  99. if (window.location.hash.length > 0){
  100. //OK to proceed
  101. try{
  102. operationConfig = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
  103. //Check if there are any missing paratmers
  104. configValid = true;
  105. if (typeof operationConfig.opr == "undefined"){ configValid = false; }
  106. if (typeof operationConfig.src == "undefined"){ configValid = false; }
  107. if (typeof operationConfig.dest == "undefined"){ configValid = false; }
  108. if (!configValid){
  109. console.log("Invalid file operation config. Please see the file_operation.html source code for the correct config json object.")
  110. ao_module_close();
  111. return;
  112. }
  113. //Parse the missing argument if there are any
  114. if (typeof operationConfig.overwriteMode == "undefined"){
  115. operationConfig.overwriteMode = "skip";
  116. }
  117. //Update 17-12-2020, ask the user for overwrite mode if file duplicate exists
  118. if (operationConfig.overwriteMode == "ask"){
  119. //Check if any file duplication before proceeding
  120. console.log(operationConfig.src);
  121. $.ajax({
  122. url: "../../system/file_system/listDir",
  123. data: {dir: operationConfig.dest},
  124. success: function(filelist){
  125. //Check fod duplication
  126. var duplicateFound = false;
  127. mainloop:
  128. for (var i = 0; i < filelist.length; i++){
  129. var desktopFile = filelist[i];
  130. for (var j = 0; j < operationConfig.src.length; j++){
  131. var srcfile = operationConfig.src[j];
  132. var srcFilename = srcfile.split("/").pop();
  133. if (srcFilename == desktopFile.Filename){
  134. duplicateFound = true
  135. break mainloop;
  136. }
  137. }
  138. };
  139. if (duplicateFound){
  140. //Duplication found.
  141. $("#duplicateAction").modal({
  142. closable: false
  143. }).modal("show");
  144. }else{
  145. //Duplication not found. Start the operation with default mode
  146. operationConfig.overwriteMode = "skip";
  147. processOperations(operationConfig);
  148. }
  149. }
  150. })
  151. }else{
  152. //All information are defined. Process it now.
  153. processOperations(operationConfig)
  154. }
  155. }catch(ex){
  156. //Failed
  157. console.log("Argument parse error", ex);
  158. }
  159. }else{
  160. alert("Invalid use of File Operation Listener.")
  161. ao_module_close();
  162. }
  163. }
  164. function continueProcess(duplicateMode){
  165. operationConfig.overwriteMode = duplicateMode;
  166. $("#duplicateAction").modal("hide");
  167. processOperations(operationConfig)
  168. }
  169. function processOperations(operationConfig){
  170. //Update the display information
  171. $("#src").text(operationConfig.src);
  172. $("#dest").text(operationConfig.dest);
  173. opr = operationConfig.opr;
  174. updateTitle(operationConfig.src.length, operationConfig.opr);
  175. //Check which type of the oprs is about. And assign the related functions to start
  176. if (operationConfig.opr == "move"){
  177. $("#opricon").attr("src","img/move.gif");
  178. cut(operationConfig.src, operationConfig.dest, operationConfig.overwriteMode);
  179. }else if (operationConfig.opr == "copy"){
  180. $("#opricon").attr("src","img/copy.gif");
  181. copy(operationConfig.src, operationConfig.dest, operationConfig.overwriteMode);
  182. }else if (operationConfig.opr == "zip"){
  183. $("#opricon").attr("src","img/zip.gif");
  184. zip(operationConfig.src, operationConfig.dest, operationConfig.overwriteMode);
  185. }else if (operationConfig.opr == "unzip"){
  186. $("#opricon").attr("src","img/unzip.gif");
  187. unzip(operationConfig.src, operationConfig.dest, operationConfig.overwriteMode);
  188. }else if (operationConfig.opr == "unzipAndOpen"){
  189. $("#opricon").attr("src","img/unzip.gif");
  190. unzip(operationConfig.src, operationConfig.dest, operationConfig.overwriteMode, function(){
  191. //Open the target directory
  192. });
  193. }
  194. }
  195. //Unzip zip
  196. function unzip(srcList, dest, overwriteMode, callback=undefined){
  197. if (legacyMode){
  198. //Run in legacy mode
  199. $.ajax({
  200. type: 'POST',
  201. url: `../../system/file_system/fileOpr`,
  202. data: {opr: "unzip" ,src: JSON.stringify(srcList), dest: dest},
  203. success: function(data){
  204. handleFinish(data);
  205. }
  206. });
  207. }else{
  208. //Filter all + sign in the list
  209. var filteredSrcList = [];
  210. srcList.forEach(src => {
  211. filteredSrcList.push(src.split("+").join("{{plug_sign}}"));
  212. });
  213. //Open WebSocket
  214. var endpoint = getWSEndpoint() + `?opr=unzip&src=${encodeURIComponent(JSON.stringify(filteredSrcList))}&dest=${encodeURIComponent(dest)}&existsresp=${overwriteMode}`
  215. console.log(endpoint);
  216. var ws = new WebSocket(endpoint);
  217. ws.onopen = function(){
  218. //Do nothing. Just listen to the progress update
  219. };
  220. ws.onmessage = function (evt) {
  221. var data = evt.data;
  222. var progress = JSON.parse(data);
  223. console.log(progress);
  224. if (progress.Error != ""){
  225. //Something went wrong
  226. $("#progressbar").css("background-color", "#eb3f28");
  227. enterErrorMode = true;
  228. alert(progress.Error);
  229. }else{
  230. //Update the progress display
  231. $("#progressbar").css("width", progress.Progress + "%");
  232. var currentSrc = truncate(progress.LatestFile, maxPathDisplayLength);
  233. var filteredDest = operationConfig.dest.trim();
  234. if (filteredDest.substr(filteredDest.length -1, 1) != "/"){
  235. filteredDest += "/"
  236. }
  237. var currentDest = truncate(filteredDest + progress.LatestFile, maxPathDisplayLength);
  238. $("#src").text(currentSrc);
  239. $("#dest").text(currentDest);
  240. $("#progress").text(progress.Progress + "%")
  241. if (progress.Progress == 100){
  242. //Set progress bar to green
  243. $("#progressbar").css("background-color", "#2bba35");
  244. }
  245. }
  246. };
  247. ws.onclose = function() {
  248. if (!enterErrorMode){
  249. $("#progressbar").css("background-color", "#2bba35");
  250. $("#progressbar").css("width", "100%");
  251. $("#progress").text("100%")
  252. setTimeout(function(){
  253. if (operationConfig.opr == "unzipAndOpen"){
  254. //If open after unzip is required
  255. ao_module_openPath(operationConfig.dest);
  256. }
  257. ao_module_close();
  258. }, 1000);
  259. }
  260. };
  261. ws.onerror = function(event){
  262. console.error("WebSocket error observed:", event);
  263. legacyMode = true;
  264. console.log("Falling back to Legacy Mode");
  265. unzip(srcList, dest, overwriteMode, callback);
  266. }
  267. }
  268. }
  269. //Create zip
  270. function zip(srcList, dest, overwriteMode){
  271. if (legacyMode){
  272. //Run in legacy mode
  273. $.ajax({
  274. type: 'POST',
  275. url: `../../system/file_system/fileOpr`,
  276. data: {opr: "zip" ,src: JSON.stringify(srcList), dest: dest},
  277. success: function(data){
  278. handleFinish(data);
  279. }
  280. });
  281. }else{
  282. //Replace all + sign with tag
  283. var filteredSrcList = [];
  284. srcList.forEach(src => {
  285. filteredSrcList.push(src.split("+").join("{{plug_sign}}"));
  286. })
  287. //Start WebSocket connection
  288. var endpoint = getWSEndpoint() + `?opr=zip&src=${encodeURIComponent(JSON.stringify(filteredSrcList))}&dest=${encodeURIComponent(dest)}&existsresp=${overwriteMode}`
  289. console.log(endpoint);
  290. var ws = new WebSocket(endpoint);
  291. var srcZipRoot = "";
  292. ws.onopen = function() {
  293. console.log("File Operation WebSocket opened")
  294. //Emulate the src folder
  295. var srcDir = srcList[0].split("/");
  296. srcDir.pop();
  297. srcDir = srcDir.join("/");
  298. srcZipRoot = srcDir;
  299. var currentSrc = truncate(srcDir, maxPathDisplayLength);
  300. $("#src").text(currentSrc);
  301. //Emulate the dest folder
  302. var destFolderName = dest.substr(0, dest.length - 1).split('/').pop();
  303. destFolderName += ".zip";
  304. var currentDest = truncate(dest + destFolderName, maxPathDisplayLength);
  305. $("#dest").text(currentDest);
  306. };
  307. ws.onmessage = function (evt) {
  308. var data = evt.data;
  309. var progress = JSON.parse(data);
  310. if (progress.Error != ""){
  311. //Something went wrong
  312. $("#progressbar").css("background-color", "#eb3f28");
  313. enterErrorMode = true;
  314. alert(progress.Error);
  315. }else{
  316. //Update the progress display
  317. $("#progressbar").css("width", progress.Progress + "%");
  318. var currentSrc = truncate(srcZipRoot + "/" + progress.LatestFile, maxPathDisplayLength);
  319. var filteredDest = operationConfig.dest.trim();
  320. if (filteredDest.substr(filteredDest.length -1, 1) != "/"){
  321. filteredDest += "/"
  322. }
  323. var currentDest = truncate(filteredDest + progress.LatestFile, maxPathDisplayLength);
  324. $("#src").text(currentSrc);
  325. $("#dest").text(currentDest);
  326. $("#progress").text(progress.Progress + "%")
  327. if (progress.Progress == 100){
  328. //Set progress bar to green
  329. $("#progressbar").css("background-color", "#2bba35");
  330. }
  331. }
  332. };
  333. ws.onclose = function() {
  334. //Transfer finished! Set OK and close in 1 second
  335. if (!enterErrorMode){
  336. $("#progressbar").css("background-color", "#2bba35");
  337. $("#progressbar").css("width", "100%");
  338. $("#progress").text("100%")
  339. setTimeout(function(){
  340. ao_module_close();
  341. }, 1000);
  342. }
  343. };
  344. ws.onerror = function(event){
  345. console.error("WebSocket error observed:", event);
  346. legacyMode = true;
  347. console.log("Falling back to Legacy Mode");
  348. zip(srcList, dest, overwriteMode);
  349. }
  350. }
  351. }
  352. function cut(srcList, dest, overwriteMode){
  353. if (legacyMode){
  354. console.log("WebSocket not found, Running in legacy mode")
  355. $.ajax({
  356. type: 'POST',
  357. url: `../../system/file_system/fileOpr`,
  358. data: {opr: "move" ,src: JSON.stringify(srcList), dest: dest,existsresp: overwriteMode},
  359. success: function(data){
  360. handleFinish(data);
  361. }
  362. });
  363. }else{
  364. //Replace all + sign in srclist with {{plus_sign}}
  365. var filteredSrcList = [];
  366. srcList.forEach(src => {
  367. filteredSrcList.push(src.split("+").join("{{plug_sign}}"));
  368. })
  369. //Use Websocket for operation updates
  370. var endpoint = getWSEndpoint() + `?opr=move&src=${encodeURIComponent(JSON.stringify(filteredSrcList))}&dest=${encodeURIComponent(dest)}&existsresp=${overwriteMode}`
  371. console.log(endpoint);
  372. var ws = new WebSocket(endpoint);
  373. ws.onopen = function() {
  374. console.log("File Operation WebSocket opened")
  375. };
  376. ws.onmessage = function (evt) {
  377. var data = evt.data;
  378. var progress = JSON.parse(data);
  379. if (progress.Error != ""){
  380. $("#progressbar").css("background-color", "#eb3f28");
  381. enterErrorMode = true;
  382. alert(progress.Error);
  383. }else{
  384. $("#progressbar").css("width", progress.Progress + "%");
  385. var currentSrc = truncate(operationConfig.src + "/" + progress.LatestFile, maxPathDisplayLength);
  386. var filteredDest = operationConfig.dest.trim();
  387. if (filteredDest.substr(filteredDest.length -1, 1) != "/"){
  388. filteredDest += "/"
  389. }
  390. var currentDest = truncate(filteredDest + progress.LatestFile, maxPathDisplayLength);
  391. $("#src").text(currentSrc);
  392. $("#dest").text(currentDest);
  393. $("#progress").text(progress.Progress + "%")
  394. if (progress.Progress == 100){
  395. //Set progress bar to green
  396. $("#progressbar").css("background-color", "#2bba35");
  397. }
  398. }
  399. };
  400. ws.onclose = function() {
  401. //Transfer finished! Set OK and close in 1 second
  402. if (!enterErrorMode){
  403. $("#progressbar").css("background-color", "#2bba35");
  404. $("#progressbar").css("width", "100%");
  405. $("#progress").text("100%")
  406. setTimeout(function(){
  407. ao_module_close();
  408. }, 1000);
  409. }
  410. };
  411. ws.onerror = function(event){
  412. console.error("WebSocket error observed:", event);
  413. console.log("Falling back to Legacy Mode")
  414. legacyMode = true;
  415. cut(srcList, dest, overwriteMode)
  416. }
  417. }
  418. }
  419. function copy(srcList, dest, overwriteMode){
  420. if (legacyMode){
  421. //Open operation in legacy mode
  422. console.log("WebSocket not found, Running in legacy mode")
  423. $.ajax({
  424. type: 'POST',
  425. url: `../../system/file_system/fileOpr`,
  426. data: {opr: "copy" ,src: JSON.stringify(srcList), dest: dest,existsresp: overwriteMode },
  427. success: function(data){
  428. handleFinish(data);
  429. }
  430. });
  431. }else{
  432. var filteredSrcList = [];
  433. srcList.forEach(src => {
  434. filteredSrcList.push(src.split("+").join("{{plug_sign}}"));
  435. })
  436. //Use Websocket for operation updates
  437. var endpoint = getWSEndpoint() + `?opr=copy&src=${JSON.stringify(filteredSrcList)}&dest=${encodeURIComponent(dest)}&existsresp=${overwriteMode}`
  438. var ws = new WebSocket(endpoint);
  439. ws.onopen = function() {
  440. console.log("File Operation WebSocket opened")
  441. };
  442. ws.onmessage = function (evt) {
  443. var data = evt.data;
  444. var progress = JSON.parse(data);
  445. if (progress.Error != ""){
  446. $("#progressbar").css("background-color", "#eb3f28");
  447. enterErrorMode = true;
  448. alert(progress.Error);
  449. }else{
  450. $("#progressbar").css("width", progress.Progress + "%");
  451. var currentSrc = truncate(operationConfig.src + "/" + progress.LatestFile, maxPathDisplayLength);
  452. var filteredDest = operationConfig.dest.trim();
  453. if (filteredDest.substr(filteredDest.length -1, 1) != "/"){
  454. filteredDest += "/"
  455. }
  456. var currentDest = truncate(filteredDest + progress.LatestFile, maxPathDisplayLength);
  457. $("#src").text(currentSrc);
  458. $("#dest").text(currentDest);
  459. $("#progress").text(progress.Progress + "%")
  460. if (progress.Progress == 100){
  461. //Set progress bar to green
  462. $("#progressbar").css("background-color", "#2bba35");
  463. }
  464. }
  465. };
  466. ws.onclose = function() {
  467. //Transfer finished! Set OK and close in 1 second
  468. if (!enterErrorMode){
  469. $("#progressbar").css("background-color", "#2bba35");
  470. $("#progressbar").css("width", "100%");
  471. $("#progress").text("100%")
  472. setTimeout(function(){
  473. ao_module_close();
  474. }, 1000);
  475. }
  476. };
  477. ws.onerror = function(event){
  478. console.error("WebSocket error observed:", event);
  479. console.log("Falling back to Legacy Mode")
  480. legacyMode = true;
  481. copy(srcList, dest, overwriteMode)
  482. }
  483. }
  484. }
  485. var truncate = function (fullStr, strLen, separator) {
  486. if (fullStr.length <= strLen) return fullStr;
  487. separator = separator || '...';
  488. var sepLen = separator.length,
  489. charsToShow = strLen - sepLen,
  490. frontChars = Math.ceil(charsToShow/2),
  491. backChars = Math.floor(charsToShow/2);
  492. return fullStr.substr(0, frontChars) +
  493. separator +
  494. fullStr.substr(fullStr.length - backChars);
  495. };
  496. function getWSEndpoint(){
  497. //Open opeartion in websocket
  498. let protocol = "wss://";
  499. if (location.protocol !== 'https:') {
  500. protocol = "ws://";
  501. }
  502. wsControlEndpoint = (protocol + window.location.hostname + ":" + window.location.port + "/system/file_system/ws/fileOpr");
  503. return wsControlEndpoint;
  504. }
  505. function updateTitle(fileNumber, opr){
  506. var title = "";
  507. if (opr == "move"){
  508. title = "Moving ";
  509. }else if (opr == "copy"){
  510. title = "Copying ";
  511. }else if (opr == "zip" || opr == "zipAndDownload"){
  512. title = "Zipping ";
  513. }else if (opr == "download"){
  514. title = "Downloading ";
  515. }else if (opr == "unzip" || opr == "unzipAndOpen"){
  516. title = "Unzipping ";
  517. }
  518. title += fileNumber + " ";
  519. if (fileNumber == 1){
  520. title += " File";
  521. }else{
  522. title += " Files";
  523. }
  524. $(".title").text(title);
  525. }
  526. function handleFinish(data){
  527. if (data.error !== undefined){
  528. $("#progressbar").css("background-color","#db2828");
  529. $("#progressbar").parent().removeClass('active');
  530. $(".title").html(`<i class="remove icon"></i>` + data.error);
  531. }else{
  532. $("#progressbar").css("background-color","#21ba45");
  533. $("#progressbar").parent().removeClass('active');
  534. if (opr == "move" || opr == "copy" || opr == "zip" || opr == "unzip"){
  535. //Action completed. close window.
  536. setTimeout(function(){
  537. //Do callback if exists
  538. if (operationConfig.callbackWindowID !== undefined && operationConfig.callbackFunction !== undefined){
  539. var callbackWindowObject = parent.getFloatWindowByID(operationConfig.callbackWindowID)
  540. var windowObject = $(callbackWindowObject).find("iframe")[0];
  541. console.log(windowObject.contentWindow, operationConfig.callbackFunction);
  542. windowObject.contentWindow.eval(operationConfig.callbackFunction);
  543. }
  544. ao_module_close();
  545. }, 1000);
  546. }else if (opr == "download"){
  547. //Create download from buffer
  548. }else if (opr == "zipAndDown"){
  549. //Download
  550. }else if (opr == "unzipAndOpen"){
  551. //Unzip and open the target directory
  552. setTimeout(function(){
  553. //Open the target directory
  554. ao_module_openPath(operationConfig.dest);
  555. //Close the window
  556. ao_module_close();
  557. }, 1000);
  558. }
  559. }
  560. console.log(data);
  561. }
  562. </script>
  563. </body>
  564. </html>