imapsync_form_wrapper.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. // $Id: imapsync_form_wrapper.js,v 1.2 2020/03/04 14:09:21 gilles Exp gilles $
  2. /*jslint browser: true*/ /*global $*/
  3. $(document).ready(
  4. function ()
  5. {
  6. "use strict";
  7. // Bootstrap popover and tooltip
  8. $("[data-toggle='tooltip']").tooltip();
  9. var readyStateStr = {
  10. "0": "Request not initialized",
  11. "1": "Server connection established",
  12. "2": "Response headers received",
  13. "3": "Processing request",
  14. "4": "Finished and response is ready"
  15. } ;
  16. var refresh_interval_ms = 5000 ;
  17. var refresh_interval_s = refresh_interval_ms / 1000 ;
  18. var test = {
  19. counter_all : 0 ,
  20. counter_ok : 0 ,
  21. counter_nok : 0 ,
  22. failed_tests : ""
  23. } ;
  24. var is = function is( expected, given, comment )
  25. {
  26. test.counter_all += 1 ;
  27. var message = test.counter_all + " - ["
  28. + expected
  29. + "] === ["
  30. + given
  31. + "] "
  32. + comment
  33. + "\n" ;
  34. if ( expected === given )
  35. {
  36. test.counter_ok += 1 ;
  37. message = "ok " + message ;
  38. }
  39. else
  40. {
  41. test.counter_nok += 1 ;
  42. test.failed_tests += "nb " + message + "\n" ;
  43. message = "not ok " + message ;
  44. }
  45. $("#tests").append( message ) ;
  46. } ;
  47. function last_eta( string )
  48. {
  49. // return the last occurrence of the substring "ETA: ...\n"
  50. // or "ETA: unknown" or ""
  51. var eta ;
  52. var last_found ;
  53. if ( undefined === string )
  54. {
  55. return "" ;
  56. }
  57. var eta_re = /ETA:.*\n/g ;
  58. eta = string.match( eta_re ) ;
  59. if ( eta )
  60. {
  61. last_found = eta[eta.length -1 ] ;
  62. return last_found ;
  63. }
  64. else
  65. {
  66. return "ETA: unknown" ;
  67. }
  68. }
  69. function tests_last_eta()
  70. {
  71. is( "", last_eta( ), "last_eta: no args => empty string" ) ;
  72. is(
  73. "ETA: unknown",
  74. last_eta( "" ),
  75. "last_eta: empty => empty string" ) ;
  76. is( "ETA: unknown",
  77. last_eta( "ETA" ),
  78. "last_eta: ETA => empty string" ) ;
  79. is( "ETA: unknown", last_eta( "ETA: but no CR" ),
  80. "last_eta: ETA: but no CR => empty string" ) ;
  81. is(
  82. "ETA: with CR\n",
  83. last_eta( "Blabla ETA: with CR\n" ),
  84. "last_eta: ETA: with CR => ETA: with CR"
  85. ) ;
  86. is(
  87. "ETA: 2 with CR\n",
  88. last_eta( "Blabla ETA: 1 with CR\nBlabla ETA: 2 with CR\n" ),
  89. "last_eta: several ETA: with CR => ETA: 2 with CR"
  90. ) ;
  91. }
  92. var tests_decompose_eta_line = function tests_decompose_eta_line()
  93. {
  94. var eta_obj ;
  95. var eta_str = "ETA: Wed Jul 3 14:55:27 2019 1234 s 123/4567 msgs left\n" ;
  96. eta_obj = decompose_eta_line( "" ) ;
  97. is(
  98. "",
  99. eta_obj.str,
  100. "decompose_eta_line: no match => undefined"
  101. ) ;
  102. eta_obj = decompose_eta_line( eta_str ) ;
  103. is(
  104. eta_str,
  105. eta_str,
  106. "decompose_eta_line: str is str"
  107. ) ;
  108. is(
  109. eta_str,
  110. eta_obj.str,
  111. "decompose_eta_line: str back"
  112. ) ;
  113. is(
  114. "Wed Jul 3 14:55:27 2019",
  115. eta_obj.date,
  116. "decompose_eta_line: date"
  117. ) ;
  118. is(
  119. "1234",
  120. eta_obj.seconds_left,
  121. "decompose_eta_line: seconds_left"
  122. ) ;
  123. is(
  124. "123",
  125. eta_obj.msgs_left,
  126. "decompose_eta_line: msgs_left"
  127. ) ;
  128. is(
  129. "4567",
  130. eta_obj.msgs_total,
  131. "decompose_eta_line: msgs_total"
  132. ) ;
  133. is(
  134. "4444",
  135. eta_obj.msgs_done(),
  136. "decompose_eta_line: msgs_done"
  137. ) ;
  138. is(
  139. "97.31",
  140. eta_obj.percent_done(),
  141. "decompose_eta_line: percent_done"
  142. ) ;
  143. is(
  144. "2.69",
  145. eta_obj.percent_left(),
  146. "decompose_eta_line: percent_left"
  147. ) ;
  148. } ;
  149. var decompose_eta_line = function decompose_eta_line( eta_str )
  150. {
  151. var eta_obj ;
  152. var eta_array ;
  153. var regex_eta = /^ETA:\s+(.*?)\s+([0-9]+)\s+s\s+([0-9]+)\/([0-9]+)\s+msgs\s+left\n?$/ ;
  154. eta_array = regex_eta.exec( eta_str ) ;
  155. if ( null !== eta_array )
  156. {
  157. eta_obj = {
  158. str : eta_str,
  159. date : eta_array[1],
  160. seconds_left : eta_array[2],
  161. msgs_left : eta_array[3],
  162. msgs_total : eta_array[4],
  163. msgs_done : function() {
  164. var diff = eta_obj.msgs_total - eta_obj.msgs_left ;
  165. return( diff.toString() ) ;
  166. },
  167. percent_done : function() {
  168. var percent ;
  169. if ( 0 === eta_obj.msgs_total )
  170. {
  171. return "0" ;
  172. }
  173. else
  174. {
  175. percent = ( eta_obj.msgs_total - eta_obj.msgs_left ) / eta_obj.msgs_total * 100 ;
  176. return( percent.toFixed(2) ) ;
  177. }
  178. },
  179. percent_left : function() {
  180. var percent ;
  181. if ( 0 === eta_obj.msgs_total )
  182. {
  183. return "0" ;
  184. }
  185. else
  186. {
  187. percent = ( eta_obj.msgs_left / eta_obj.msgs_total * 100 ) ;
  188. return( percent.toFixed(2) ) ;
  189. }
  190. }
  191. } ;
  192. }
  193. else
  194. {
  195. eta_obj = {
  196. str : "",
  197. date : "?",
  198. seconds_left : "?",
  199. msgs_left : "?",
  200. msgs_total : "?",
  201. msgs_done : "?",
  202. percent_done : function() { return "" ; },
  203. percent_left : function() { return "" ; }
  204. } ;
  205. }
  206. return eta_obj ;
  207. } ;
  208. var extract_eta = function extract_eta( xhr )
  209. {
  210. var eta_obj ;
  211. var slice_length ;
  212. var slice_log ;
  213. var eta_str ;
  214. if ( xhr.readyState === 4 )
  215. {
  216. slice_length = -24000 ;
  217. }
  218. else
  219. {
  220. slice_length = -240 ;
  221. }
  222. slice_log = xhr.responseText.slice( slice_length ) ;
  223. eta_str = last_eta( slice_log ) ;
  224. // $("#tests").append( "extract_eta eta_str: " + eta_str + "\n" ) ;
  225. eta_obj = decompose_eta_line( eta_str ) ;
  226. return eta_obj ;
  227. } ;
  228. var progress_bar_update = function progress_bar_update( eta_obj )
  229. {
  230. if ( eta_obj.str.length )
  231. {
  232. $("#progress-bar-done").css( "width", eta_obj.percent_done() + "%" ).attr( "aria-valuenow", eta_obj.percent_done() ) ;
  233. $("#progress-bar-left").css( "width", eta_obj.percent_left() + "%" ).attr( "aria-valuenow", eta_obj.percent_left() ) ;
  234. $("#progress-bar-done").text( eta_obj.percent_done() + "% " + "done" ) ;
  235. $("#progress-bar-left").text( eta_obj.percent_left() + "% " + "left" ) ;
  236. }
  237. else
  238. {
  239. $("#progress-bar-done").text( "unknown % " + "done" ) ;
  240. $("#progress-bar-left").text( "unknown % " + "left" ) ;
  241. }
  242. return ;
  243. } ;
  244. function refreshLog( xhr )
  245. {
  246. var eta_obj ;
  247. var eta_str ;
  248. eta_obj = extract_eta( xhr ) ;
  249. progress_bar_update( eta_obj ) ;
  250. if ( xhr.readyState === 4 )
  251. {
  252. // end of sync
  253. $("#progress-txt").text(
  254. "Ended. It remains "
  255. + eta_obj.msgs_left + " messages to be synced" ) ;
  256. }
  257. else
  258. {
  259. eta_str = eta_obj.str + " (refresh every " + refresh_interval_s + " s)" ;
  260. eta_str = eta_str.replace(/(\r\n|\n|\r)/gm, "") ; // trim newline
  261. //$("#tests").append( "refreshLog eta_str: " + eta_str + "\n" ) ;
  262. $("#progress-txt").text( eta_str ) ;
  263. }
  264. $( "#output" ).text( xhr.responseText ) ;
  265. }
  266. function handleRun(xhr, timerRefreshLog)
  267. {
  268. $("#console").text(
  269. "Status: " + xhr.status + " " + xhr.statusText + "\n"
  270. + "State: " + readyStateStr[xhr.readyState] + "\n" ) ;
  271. if ( xhr.readyState === 4 ) {
  272. // var headers = xhr.getAllResponseHeaders();
  273. // $("#console").append(headers);
  274. // $("#console").append("See the completed log\n");
  275. $("#link_to_bottom").show() ;
  276. clearInterval( timerRefreshLog ) ;
  277. refreshLog( xhr ) ; // a last time
  278. // back to enable state for next run
  279. $("#bt-sync").prop("disabled", false) ;
  280. }
  281. }
  282. function imapsync()
  283. {
  284. var querystring = $("#form").serialize() ;
  285. $("#abort").text("\n\n") ; // clean abort console
  286. $("#output").text("Here comes the log!\n\n") ;
  287. if ( "imap.gmail.com" === $("#host1").val() )
  288. {
  289. querystring = querystring + "&gmail1=on" ;
  290. }
  291. if ( "imap.gmail.com" === $("#host2").val() )
  292. {
  293. querystring = querystring + "&gmail2=on" ;
  294. }
  295. // Same for "outlook.office365.com"
  296. if ( "outlook.office365.com" === $("#host1").val() )
  297. {
  298. querystring = querystring + "&office1=on" ;
  299. }
  300. if ( "outlook.office365.com" === $("#host2").val() )
  301. {
  302. querystring = querystring + "&office2=on" ;
  303. }
  304. var xhr ;
  305. xhr = new XMLHttpRequest() ;
  306. var timerRefreshLog = setInterval(
  307. function ()
  308. {
  309. refreshLog( xhr ) ;
  310. }, refresh_interval_ms ) ;
  311. xhr.onreadystatechange = function ()
  312. {
  313. handleRun( xhr, timerRefreshLog ) ;
  314. } ;
  315. xhr.open( "POST", "/cgi-bin/imapsync_shell_wrapper", true ) ;
  316. xhr.setRequestHeader( "Content-type",
  317. "application/x-www-form-urlencoded" ) ;
  318. xhr.send( querystring ) ;
  319. }
  320. function handleAbort( xhr )
  321. {
  322. $( "#abort" ).text(
  323. "Status: " + xhr.status + " " + xhr.statusText + "\n"
  324. + "State: " + readyStateStr[xhr.readyState] + "\n\n" ) ;
  325. if ( xhr.readyState === 4 )
  326. {
  327. $("#abort").append(xhr.responseText);
  328. $("#bt-sync").prop("disabled", false);
  329. $("#bt-abort").prop("disabled", false); // back for next abort
  330. }
  331. }
  332. function abort()
  333. {
  334. var querystring = $("#form").serialize() + "&abort=on";
  335. var xhr;
  336. xhr = new XMLHttpRequest();
  337. xhr.onreadystatechange = function ()
  338. {
  339. handleAbort(xhr);
  340. };
  341. xhr.open("POST", "/cgi-bin/imapsync_shell_wrapper", true);
  342. xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  343. xhr.send(querystring);
  344. }
  345. function store( id )
  346. {
  347. var stored ;
  348. //$( "#tests" ).append( "Eco: " + id + " type is " + $( id ).attr( "type" ) + "\n" ) ;
  349. if ( "text" === $( id ).attr( "type" ) || "password" === $( id ).attr( "type" ) )
  350. {
  351. localStorage.setItem( id, $(id).val() ) ;
  352. stored = $( id ).val() ;
  353. }
  354. else if ( "checkbox" === $( id ).attr( "type" ) )
  355. {
  356. //$( "#tests" ).append( "Eco: " + id + " checked is " + $( id )[0].checked + "\n" ) ;
  357. localStorage.setItem( id, $( id )[0].checked ) ;
  358. stored = $( id )[0].checked ;
  359. }
  360. return stored ;
  361. }
  362. function retrieve( id )
  363. {
  364. var retrieved ;
  365. //$( "#tests" ).append( "Eco: " + id + " type is " + $( id ).attr( "type" ) + " length is " + $( id ).length + "\n" ) ;
  366. if ( "text" === $( id ).attr( "type" ) || "password" === $( id ).attr( "type" ) )
  367. {
  368. $( id ).val( localStorage.getItem( id ) ) ;
  369. retrieved = $( id ).val() ;
  370. }
  371. else if ( "checkbox" === $( id ).attr( "type" ) )
  372. {
  373. //$( "#tests" ).append( "Eco: " + id + " getItem is " + localStorage.getItem( id ) + "\n" ) ;
  374. $( id )[0].checked = JSON.parse( localStorage.getItem( id ) ) ;
  375. retrieved = $( id )[0].checked ;
  376. }
  377. return retrieved ;
  378. }
  379. function tests_store_retrieve()
  380. {
  381. if ( $("#tests").length !== 0 )
  382. {
  383. is( 1, 1, "one equals one" ) ;
  384. // isnot( 0, 1, "zero differs one" ) ;
  385. // no exist
  386. is( undefined, store( "#test_noexists" ),
  387. "store: #test_noexists" ) ;
  388. is( undefined, retrieve( "#test_noexists" ),
  389. "retrieve: #test_noexists" ) ;
  390. is( undefined, retrieve( "#test_noexists2" ),
  391. "retrieve: #test_noexists2" ) ;
  392. // input text
  393. $("#test_text" ).val( "foo" ) ;
  394. is( "foo", $("#test_text" ).val( ), "#test_text val = foo" ) ;
  395. is( "foo", store( "#test_text" ), "store: #test_text" ) ;
  396. $("#test_text" ).val( "bar" ) ;
  397. is( "bar", $("#test_text" ).val( ), "#test_text val = bar" ) ;
  398. is( "foo", retrieve( "#test_text" ), "retrieve: #test_text = foo" ) ;
  399. is( "foo", $("#test_text" ).val( ), "#test_text val = foo" ) ;
  400. // input check button
  401. $( "#test_checkbox" ).prop( "checked", true );
  402. is( true, store( "#test_checkbox" ), "store: #test_checkbox checked" ) ;
  403. $( "#test_checkbox" ).prop( "checked", false );
  404. is( true, retrieve( "#test_checkbox" ), "retrieve: #test_checkbox = true" ) ;
  405. $( "#test_checkbox" ).prop( "checked", false );
  406. is( false, store( "#test_checkbox" ), "store: #test_checkbox not checked" ) ;
  407. $( "#test_checkbox" ).prop( "checked", true );
  408. is( false, retrieve( "#test_checkbox" ), "retrieve: #test_checkbox = false" ) ;
  409. }
  410. }
  411. function store_form()
  412. {
  413. if ( Storage !== "undefined")
  414. {
  415. // Code for localStorage.
  416. store("#user1") ;
  417. store("#password1") ;
  418. store("#host1") ;
  419. store("#subfolder1") ;
  420. store("#showpassword1") ;
  421. store("#user2") ;
  422. store("#password2") ;
  423. store("#host2") ;
  424. store("#subfolder2") ;
  425. store("#showpassword2") ;
  426. store("#dry") ;
  427. store("#justlogin") ;
  428. store("#justfolders") ;
  429. store("#justfoldersizes") ;
  430. localStorage.account1_background_color = $("#account1").css("background-color") ;
  431. localStorage.account2_background_color = $("#account2").css("background-color") ;
  432. }
  433. }
  434. function show_extra_if_needed()
  435. {
  436. if ( $("#subfolder1").length && $("#subfolder1").val().length > 0 )
  437. {
  438. $(".extra_param").show() ;
  439. }
  440. if ( $("#subfolder2").length && $("#subfolder2").val().length > 0 )
  441. {
  442. $(".extra_param").show() ;
  443. }
  444. }
  445. function retrieve_form()
  446. {
  447. if ( Storage !== "undefined" )
  448. {
  449. // Code for localStorage.
  450. retrieve( "#user1" ) ;
  451. retrieve( "#password1" ) ;
  452. // retrieve("#showpassword1") ;
  453. retrieve( "#host1" ) ;
  454. retrieve( "#subfolder1" ) ;
  455. retrieve( "#user2" ) ;
  456. retrieve( "#password2" ) ;
  457. // retrieve("#showpassword2") ;
  458. retrieve( "#host2" ) ;
  459. retrieve( "#subfolder2" ) ;
  460. retrieve( "#dry" ) ;
  461. retrieve( "#justlogin" ) ;
  462. retrieve( "#justfolders" ) ;
  463. retrieve( "#justfoldersizes" ) ;
  464. // In case, how to restore the original color from css file.
  465. // localStorage.removeItem( "account1_background_color" ) ;
  466. // localStorage.removeItem( "account2_background_color" ) ;
  467. if ( localStorage.account1_background_color )
  468. {
  469. $("#account1").css("background-color",
  470. localStorage.account1_background_color ) ;
  471. }
  472. if ( localStorage.account2_background_color )
  473. {
  474. $("#account2").css("background-color",
  475. localStorage.account2_background_color ) ;
  476. }
  477. // Show the extra parameters if they are not empty because it would be dangerous
  478. // to retrieve them without knowing
  479. show_extra_if_needed() ;
  480. }
  481. }
  482. function showpassword( id, button )
  483. {
  484. var x = document.getElementById( id );
  485. if ( button.checked )
  486. {
  487. x.type = "text";
  488. } else {
  489. x.type = "password";
  490. }
  491. }
  492. function init()
  493. {
  494. // in case of a manual refresh, start with
  495. $("#bt-sync").prop("disabled", false);
  496. $("#bt-abort").prop("disabled", false);
  497. $("#link_to_bottom").hide();
  498. $("#progress-bar-left").css( "width", 100 + "%" ).attr( "aria-valuenow", 100 ) ;
  499. retrieve_form();
  500. $("#showpassword1").click(
  501. function ( event )
  502. {
  503. var button = event.target ;
  504. showpassword( "password1", button ) ;
  505. }
  506. );
  507. $("#showpassword2").click(
  508. function ( event )
  509. {
  510. //$("#tests").append( "\nthat1=" + JSON.stringify( event.target, undefined, 4 ) ) ;
  511. var button = event.target ;
  512. showpassword( "password2", button ) ;
  513. }
  514. );
  515. $("#bt-sync").click(
  516. function ()
  517. {
  518. $("#bt-sync").prop("disabled", true) ;
  519. $("#bt-abort").prop("disabled", false) ;
  520. $("#link_to_bottom").hide() ;
  521. $("#progress-txt").text( "ETA: coming soon" ) ;
  522. store_form() ;
  523. imapsync() ;
  524. }
  525. );
  526. $("#bt-abort").click(
  527. function ()
  528. {
  529. $("#bt-sync").prop("disabled", true);
  530. $("#bt-abort").prop("disabled", true);
  531. abort();
  532. }
  533. );
  534. var swap = function swap( p1, p2 )
  535. {
  536. var temp = $( p2 ).val( ) ;
  537. $( p2 ).val( $( p1 ).val( ) ) ;
  538. $( p1 ).val( temp ) ;
  539. } ;
  540. $("#swap").click(
  541. function()
  542. {
  543. // swaping colors can't use swap()
  544. var temp1 = $("#account1").css("background-color") ;
  545. var temp2 = $("#account2").css("background-color") ;
  546. $("#account1").css("background-color", temp2 );
  547. $("#account2").css("background-color", temp1 );
  548. swap( $("#user1"), $("#user2") ) ;
  549. swap( $("#password1"), $("#password2") ) ;
  550. swap( $("#host1"), $("#host2") ) ;
  551. swap( $("#subfolder1"), $("#subfolder2") ) ;
  552. var temp = $("#showpassword1")[0].checked ;
  553. $("#showpassword1")[0].checked = $("#showpassword2")[0].checked ;
  554. $("#showpassword2")[0].checked = temp ;
  555. showpassword( "password1", $("#showpassword1")[0] ) ;
  556. showpassword( "password2", $("#showpassword2")[0] ) ;
  557. }
  558. ) ;
  559. }
  560. var tests_bilan = function tests_bilan()
  561. {
  562. // attended number of tests
  563. var nb_attended_test = 29 ;
  564. $("#tests").append( "1.." + test.counter_all + "\n" ) ;
  565. if ( test.counter_nok > 0 )
  566. {
  567. $("#tests").append(
  568. "\nFAILED tests \n"
  569. + test.failed_tests
  570. ) ;
  571. $("#tests").collapse("show") ;
  572. }
  573. // Summary of tests: failed 0 tests, run xx tests,
  574. // expected to run yy tests.
  575. if ( test.counter_all !== nb_attended_test )
  576. {
  577. $("#tests").append( "# Looks like you planned "
  578. + nb_attended_test
  579. + " tests but ran "
  580. + test.counter_all + ".\n"
  581. ) ;
  582. $("#tests").collapse("show") ;
  583. }
  584. } ;
  585. function tests()
  586. {
  587. if ( $("#tests").length !== 0 )
  588. {
  589. tests_store_retrieve( ) ;
  590. tests_last_eta( ) ;
  591. tests_decompose_eta_line( ) ;
  592. //is( 0, 1, "this test always fails" ) ;
  593. tests_bilan( ) ;
  594. //$("#tests").collapse("show") ;
  595. }
  596. }
  597. init( ) ;
  598. tests( ) ;
  599. }
  600. );