file_operation.html 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  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. $("#opricon").attr("src", "img/error.png");
  228. enterErrorMode = true;
  229. alert(progress.Error);
  230. }else{
  231. //Update the progress display
  232. $("#progressbar").css("width", progress.Progress + "%");
  233. var currentSrc = truncate(progress.LatestFile, maxPathDisplayLength);
  234. var filteredDest = operationConfig.dest.trim();
  235. if (filteredDest.substr(filteredDest.length -1, 1) != "/"){
  236. filteredDest += "/"
  237. }
  238. var currentDest = truncate(filteredDest + progress.LatestFile, maxPathDisplayLength);
  239. $("#src").text(currentSrc);
  240. $("#dest").text(currentDest);
  241. $("#progress").text(progress.Progress + "%")
  242. if (progress.Progress == 100){
  243. //Set progress bar to green
  244. $("#progressbar").css("background-color", "#2bba35");
  245. }
  246. }
  247. };
  248. ws.onclose = function() {
  249. if (!enterErrorMode){
  250. $("#progressbar").css("background-color", "#2bba35");
  251. $("#progressbar").css("width", "100%");
  252. $("#progress").text("100%");
  253. $("#opricon").attr("src", "img/done.png")
  254. setTimeout(function(){
  255. if (operationConfig.opr == "unzipAndOpen"){
  256. //If open after unzip is required
  257. ao_module_openPath(operationConfig.dest);
  258. }
  259. ao_module_close();
  260. }, 1000);
  261. }
  262. };
  263. ws.onerror = function(event){
  264. console.error("WebSocket error observed:", event);
  265. legacyMode = true;
  266. console.log("Falling back to Legacy Mode");
  267. unzip(srcList, dest, overwriteMode, callback);
  268. }
  269. }
  270. }
  271. //Create zip
  272. function zip(srcList, dest, overwriteMode){
  273. if (legacyMode){
  274. //Run in legacy mode
  275. $.ajax({
  276. type: 'POST',
  277. url: `../../system/file_system/fileOpr`,
  278. data: {opr: "zip" ,src: JSON.stringify(srcList), dest: dest},
  279. success: function(data){
  280. handleFinish(data);
  281. }
  282. });
  283. }else{
  284. //Replace all + sign with tag
  285. var filteredSrcList = [];
  286. srcList.forEach(src => {
  287. filteredSrcList.push(src.split("+").join("{{plug_sign}}"));
  288. })
  289. //Start WebSocket connection
  290. var endpoint = getWSEndpoint() + `?opr=zip&src=${encodeURIComponent(JSON.stringify(filteredSrcList))}&dest=${encodeURIComponent(dest)}&existsresp=${overwriteMode}`
  291. console.log(endpoint);
  292. var ws = new WebSocket(endpoint);
  293. var srcZipRoot = "";
  294. ws.onopen = function() {
  295. console.log("File Operation WebSocket opened")
  296. //Emulate the src folder
  297. var srcDir = srcList[0].split("/");
  298. srcDir.pop();
  299. srcDir = srcDir.join("/");
  300. srcZipRoot = srcDir;
  301. var currentSrc = truncate(srcDir, maxPathDisplayLength);
  302. $("#src").text(currentSrc);
  303. //Emulate the dest folder
  304. var destFolderName = dest.substr(0, dest.length - 1).split('/').pop();
  305. destFolderName += ".zip";
  306. var currentDest = truncate(dest + destFolderName, maxPathDisplayLength);
  307. $("#dest").text(currentDest);
  308. };
  309. ws.onmessage = function (evt) {
  310. var data = evt.data;
  311. var progress = JSON.parse(data);
  312. if (progress.Error != ""){
  313. //Something went wrong
  314. $("#progressbar").css("background-color", "#eb3f28");
  315. enterErrorMode = true;
  316. alert(progress.Error);
  317. }else{
  318. //Update the progress display
  319. $("#progressbar").css("width", progress.Progress + "%");
  320. var currentSrc = truncate(srcZipRoot + "/" + progress.LatestFile, maxPathDisplayLength);
  321. var filteredDest = operationConfig.dest.trim();
  322. if (filteredDest.substr(filteredDest.length -1, 1) != "/"){
  323. filteredDest += "/"
  324. }
  325. var currentDest = truncate(filteredDest + progress.LatestFile, maxPathDisplayLength);
  326. $("#src").text(currentSrc);
  327. $("#dest").text(currentDest);
  328. $("#progress").text(progress.Progress + "%")
  329. if (progress.Progress == 100){
  330. //Set progress bar to green
  331. $("#progressbar").css("background-color", "#2bba35");
  332. }
  333. }
  334. };
  335. ws.onclose = function() {
  336. //Transfer finished! Set OK and close in 1 second
  337. if (!enterErrorMode){
  338. $("#progressbar").css("background-color", "#2bba35");
  339. $("#progressbar").css("width", "100%");
  340. $("#progress").text("100%")
  341. setTimeout(function(){
  342. ao_module_close();
  343. }, 1000);
  344. }
  345. };
  346. ws.onerror = function(event){
  347. console.error("WebSocket error observed:", event);
  348. legacyMode = true;
  349. console.log("Falling back to Legacy Mode");
  350. zip(srcList, dest, overwriteMode);
  351. }
  352. }
  353. }
  354. function cut(srcList, dest, overwriteMode){
  355. if (legacyMode){
  356. console.log("WebSocket not found, Running in legacy mode")
  357. $.ajax({
  358. type: 'POST',
  359. url: `../../system/file_system/fileOpr`,
  360. data: {opr: "move" ,src: JSON.stringify(srcList), dest: dest,existsresp: overwriteMode},
  361. success: function(data){
  362. handleFinish(data);
  363. }
  364. });
  365. }else{
  366. //Replace all + sign in srclist with {{plus_sign}}
  367. var filteredSrcList = [];
  368. srcList.forEach(src => {
  369. filteredSrcList.push(src.split("+").join("{{plug_sign}}"));
  370. })
  371. //Use Websocket for operation updates
  372. var endpoint = getWSEndpoint() + `?opr=move&src=${encodeURIComponent(JSON.stringify(filteredSrcList))}&dest=${encodeURIComponent(dest)}&existsresp=${overwriteMode}`
  373. console.log(endpoint);
  374. var ws = new WebSocket(endpoint);
  375. ws.onopen = function() {
  376. console.log("File Operation WebSocket opened")
  377. };
  378. ws.onmessage = function (evt) {
  379. var data = evt.data;
  380. var progress = JSON.parse(data);
  381. if (progress.Error != ""){
  382. $("#progressbar").css("background-color", "#eb3f28");
  383. enterErrorMode = true;
  384. alert(progress.Error);
  385. }else{
  386. $("#progressbar").css("width", progress.Progress + "%");
  387. var currentSrc = truncate(operationConfig.src + "/" + progress.LatestFile, maxPathDisplayLength);
  388. var filteredDest = operationConfig.dest.trim();
  389. if (filteredDest.substr(filteredDest.length -1, 1) != "/"){
  390. filteredDest += "/"
  391. }
  392. var currentDest = truncate(filteredDest + progress.LatestFile, maxPathDisplayLength);
  393. $("#src").text(currentSrc);
  394. $("#dest").text(currentDest);
  395. $("#progress").text(progress.Progress + "%")
  396. if (progress.Progress == 100){
  397. //Set progress bar to green
  398. $("#progressbar").css("background-color", "#2bba35");
  399. }
  400. }
  401. };
  402. ws.onclose = function() {
  403. //Transfer finished! Set OK and close in 1 second
  404. if (!enterErrorMode){
  405. $("#progressbar").css("background-color", "#2bba35");
  406. $("#progressbar").css("width", "100%");
  407. $("#progress").text("100%")
  408. setTimeout(function(){
  409. ao_module_close();
  410. }, 1000);
  411. }
  412. };
  413. ws.onerror = function(event){
  414. console.error("WebSocket error observed:", event);
  415. console.log("Falling back to Legacy Mode")
  416. legacyMode = true;
  417. cut(srcList, dest, overwriteMode)
  418. }
  419. }
  420. }
  421. function copy(srcList, dest, overwriteMode){
  422. if (legacyMode){
  423. //Open operation in legacy mode
  424. console.log("WebSocket not found, Running in legacy mode")
  425. $.ajax({
  426. type: 'POST',
  427. url: `../../system/file_system/fileOpr`,
  428. data: {opr: "copy" ,src: JSON.stringify(srcList), dest: dest,existsresp: overwriteMode },
  429. success: function(data){
  430. handleFinish(data);
  431. }
  432. });
  433. }else{
  434. var filteredSrcList = [];
  435. srcList.forEach(src => {
  436. filteredSrcList.push(src.split("+").join("{{plug_sign}}"));
  437. })
  438. //Use Websocket for operation updates
  439. var endpoint = getWSEndpoint() + `?opr=copy&src=${JSON.stringify(filteredSrcList)}&dest=${encodeURIComponent(dest)}&existsresp=${overwriteMode}`
  440. var ws = new WebSocket(endpoint);
  441. ws.onopen = function() {
  442. console.log("File Operation WebSocket opened")
  443. };
  444. ws.onmessage = function (evt) {
  445. var data = evt.data;
  446. var progress = JSON.parse(data);
  447. if (progress.Error != ""){
  448. $("#progressbar").css("background-color", "#eb3f28");
  449. enterErrorMode = true;
  450. alert(progress.Error);
  451. }else{
  452. $("#progressbar").css("width", progress.Progress + "%");
  453. var currentSrc = truncate(operationConfig.src + "/" + progress.LatestFile, maxPathDisplayLength);
  454. var filteredDest = operationConfig.dest.trim();
  455. if (filteredDest.substr(filteredDest.length -1, 1) != "/"){
  456. filteredDest += "/"
  457. }
  458. var currentDest = truncate(filteredDest + progress.LatestFile, maxPathDisplayLength);
  459. $("#src").text(currentSrc);
  460. $("#dest").text(currentDest);
  461. $("#progress").text(progress.Progress + "%")
  462. if (progress.Progress == 100){
  463. //Set progress bar to green
  464. $("#progressbar").css("background-color", "#2bba35");
  465. }
  466. }
  467. };
  468. ws.onclose = function() {
  469. //Transfer finished! Set OK and close in 1 second
  470. if (!enterErrorMode){
  471. $("#progressbar").css("background-color", "#2bba35");
  472. $("#progressbar").css("width", "100%");
  473. $("#progress").text("100%")
  474. setTimeout(function(){
  475. ao_module_close();
  476. }, 1000);
  477. }
  478. };
  479. ws.onerror = function(event){
  480. console.error("WebSocket error observed:", event);
  481. console.log("Falling back to Legacy Mode")
  482. legacyMode = true;
  483. copy(srcList, dest, overwriteMode)
  484. }
  485. }
  486. }
  487. var truncate = function (fullStr, strLen, separator) {
  488. if (fullStr.length <= strLen) return fullStr;
  489. separator = separator || '...';
  490. var sepLen = separator.length,
  491. charsToShow = strLen - sepLen,
  492. frontChars = Math.ceil(charsToShow/2),
  493. backChars = Math.floor(charsToShow/2);
  494. return fullStr.substr(0, frontChars) +
  495. separator +
  496. fullStr.substr(fullStr.length - backChars);
  497. };
  498. function getWSEndpoint(){
  499. //Open opeartion in websocket
  500. let protocol = "wss://";
  501. if (location.protocol !== 'https:') {
  502. protocol = "ws://";
  503. }
  504. wsControlEndpoint = (protocol + window.location.hostname + ":" + window.location.port + "/system/file_system/ws/fileOpr");
  505. return wsControlEndpoint;
  506. }
  507. function updateTitle(fileNumber, opr){
  508. var title = "";
  509. if (opr == "move"){
  510. title = "Moving ";
  511. }else if (opr == "copy"){
  512. title = "Copying ";
  513. }else if (opr == "zip" || opr == "zipAndDownload"){
  514. title = "Zipping ";
  515. }else if (opr == "download"){
  516. title = "Downloading ";
  517. }else if (opr == "unzip" || opr == "unzipAndOpen"){
  518. title = "Unzipping ";
  519. }
  520. title += fileNumber + " ";
  521. if (fileNumber == 1){
  522. title += " File";
  523. }else{
  524. title += " Files";
  525. }
  526. $(".title").text(title);
  527. }
  528. function handleFinish(data){
  529. if (data.error !== undefined){
  530. $("#progressbar").css("background-color","#db2828");
  531. $("#progressbar").parent().removeClass('active');
  532. $(".title").html(`<i class="remove icon"></i>` + data.error);
  533. }else{
  534. $("#progressbar").css("background-color","#21ba45");
  535. $("#progressbar").parent().removeClass('active');
  536. if (opr == "move" || opr == "copy" || opr == "zip" || opr == "unzip"){
  537. //Action completed. close window.
  538. setTimeout(function(){
  539. //Do callback if exists
  540. if (operationConfig.callbackWindowID !== undefined && operationConfig.callbackFunction !== undefined){
  541. var callbackWindowObject = parent.getFloatWindowByID(operationConfig.callbackWindowID)
  542. var windowObject = $(callbackWindowObject).find("iframe")[0];
  543. console.log(windowObject.contentWindow, operationConfig.callbackFunction);
  544. windowObject.contentWindow.eval(operationConfig.callbackFunction);
  545. }
  546. ao_module_close();
  547. }, 1000);
  548. }else if (opr == "download"){
  549. //Create download from buffer
  550. }else if (opr == "zipAndDown"){
  551. //Download
  552. }else if (opr == "unzipAndOpen"){
  553. //Unzip and open the target directory
  554. setTimeout(function(){
  555. //Open the target directory
  556. ao_module_openPath(operationConfig.dest);
  557. //Close the window
  558. ao_module_close();
  559. }, 1000);
  560. }
  561. }
  562. console.log(data);
  563. }
  564. </script>
  565. </body>
  566. </html>