home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Multimedia / Songbird / Songbird_2.0.0-2311_windows-i686-msvc8.exe / components / sbMoveRenameHelper.js < prev    next >
Text File  |  2012-06-08  |  13KB  |  418 lines

  1. /**
  2. //
  3. // BEGIN SONGBIRD GPL
  4. // 
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 POTI, Inc.
  8. // http://songbirdnest.com
  9. // 
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. // 
  13. // Software distributed under the License is distributed 
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
  15. // express or implied. See the GPL for the specific language 
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this 
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc., 
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. // 
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26. const Cc = Components.classes;
  27. const Ci = Components.interfaces;
  28. const Cr = Components.results;
  29. const Ce = Components.Exception;
  30. const Cu = Components.utils;
  31.  
  32. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  33. Cu.import("resource://app/jsmodules/SBJobUtils.jsm");
  34. Cu.import("resource://app/jsmodules/ArrayConverter.jsm");
  35. Cu.import("resource://app/jsmodules/sbProperties.jsm");
  36. Cu.import("resource://app/jsmodules/sbLibraryUtils.jsm");
  37. Cu.import("resource://app/jsmodules/StringUtils.jsm");
  38. Cu.import("resource://app/jsmodules/GeneratorThread.jsm");
  39.  
  40. function LOG(aMessage) {
  41.   dump("MoveRenameJob: " + aMessage + "\n");
  42. }
  43.  
  44. /******************************************************************************
  45.  * Object implementing sbIJobProgress, responsible for taking a list of
  46.  * removed paths, and a list of added paths, and figuring out if 
  47.  * media items have been moved or renamed as a result.  
  48.  *
  49.  * See sbIWFMoveRenameHelper9000 for further explanation.
  50.  *
  51.  * Call begin() to start the job.
  52.  *****************************************************************************/
  53. function MoveRenameJob(aItems, 
  54.                        aPaths, 
  55.                        aService,
  56.                        aListener)
  57. {
  58.   if (!aItems || !aPaths) {
  59.     throw Components.results.NS_ERROR_INVALID_ARG;
  60.   }
  61.  
  62.   // Call super constructor
  63.   SBJobUtils.JobBase.call(this);
  64.  
  65.   this._owner = aService;
  66.   this._listener = aListener;
  67.   this._mediaItems = ArrayConverter.JSArray(aItems);
  68.   this._paths = aPaths;
  69.   
  70.   this._map = {};
  71.     
  72.   this._titleText = SBString("watchfolders.moverename.title");
  73.   this._statusText =  SBString("watchfolders.moverename.processing");
  74.  
  75.   this._canCancel = true;  
  76. }
  77. MoveRenameJob.prototype = {
  78.   __proto__: SBJobUtils.JobBase.prototype,
  79.   
  80.   QueryInterface: XPCOMUtils.generateQI(
  81.     [Ci.sbIJobProgress, Ci.sbIJobProgressListener,
  82.      Ci.sbIJobCancelable, Ci.nsIClassInfo]),
  83.   
  84.   /** For nsIClassInfo **/
  85.   getInterfaces: function(count) {
  86.     var interfaces = [Ci.sbIJobProgress, Ci.sbIJobProgressListener,
  87.                       Ci.sbIJobCancelable, Ci.nsIClassInfo, 
  88.                       Ci.nsISupports];
  89.     count.value = interfaces.length;
  90.     return interfaces;
  91.   },
  92.  
  93.   // MoveRenameHelperService
  94.   _owner: null,
  95.   _listener: null,
  96.   
  97.   _thread: null,
  98.   _mediaItems: null,
  99.   _paths: null,
  100.   
  101.   // map[filesize][leafname] = path, based on _paths, built by _buildMap
  102.   // used as a lookup table when figuring out which media item
  103.   // to associate with which new path
  104.   _map: null,
  105.   
  106.   /**
  107.    * Begin the import job
  108.    */
  109.   begin: function MoveRenameJob_begin() {
  110.     var job = this;
  111.  
  112.     // Add the specified job listener to the super class.
  113.     this.addJobProgressListener(this._listener);
  114.  
  115.     this._thread = new GeneratorThread(this._process());
  116.     this._thread.period = 33;
  117.     this._thread.maxPctCPU = 60;
  118.     this._thread.start();
  119.   },
  120.   
  121.   /**
  122.    * Main run method, driven by _thread
  123.    */
  124.   _process: function MoveRenameJob_process() {
  125.     try { 
  126.       yield this._buildMap();
  127.       yield this._processMovedItems();
  128.       yield this._processRenamedItems();
  129.       yield this._processRemaining();
  130.     } catch (e) {
  131.       dump(e);
  132.     }
  133.     
  134.     // Complete, unless we've started a sub-job, 
  135.     // in which case it will call complete
  136.     if (!this._jobProgressDelegate) {
  137.       this.complete(); 
  138.     }
  139.   },
  140.   
  141.   /**
  142.    * Map all new paths by leafname and file size
  143.    */
  144.   _buildMap: function MoveRenameJob__buildMap() {
  145.     for (var path in ArrayConverter.JSEnum(this._paths)) {
  146.       var file = path.QueryInterface(Ci.nsIFileURL).file;
  147.       var size = "" + file.fileSize; 
  148.       var name = file.leafName;
  149.       if (!(size in this._map)) this._map[size] = {};
  150.       if (!(name in this._map[size])) this._map[size][name] = [];
  151.       this._map[size][name].push(path);
  152.       yield this._yieldIfShouldWithStatusUpdate();
  153.     }
  154.     this._paths = null;
  155.     //LOG(uneval(this._map));
  156.   },
  157.   
  158.   /**
  159.    * Use the name/filesize map to guess moved files
  160.    */
  161.   _processMovedItems: function MoveRenameJob__processMovedItems() {
  162.     this._total = this._mediaItems.length;
  163.        
  164.     var remainingItems = [];
  165.     for each (var item in this._mediaItems) {
  166.       var file = item.contentSrc.QueryInterface(Ci.nsIFileURL).file;
  167.       var name = file.leafName;
  168.       this._progress++;
  169.       var size = item.getProperty(SBProperties.contentLength);
  170.       // If we have exactly one file with the same
  171.       // name and size, it is probably the same file
  172.       if (this._map[size] && this._map[size][name] && 
  173.           this._map[size][name].length == 1) {
  174.         //LOG("found moved item " + item.contentSrc.spec);
  175.         item.contentSrc = this._map[size][name][0];
  176.         delete this._map[size][name];
  177.       } else {
  178.         //LOG(item.contentSrc.spec + " was not moved");
  179.         remainingItems.push(item);
  180.       }
  181.       
  182.       yield this._yieldIfShouldWithStatusUpdate();
  183.     }
  184.     
  185.     this._mediaItems = remainingItems;
  186.   },
  187.  
  188.   /**
  189.    * Get desperate and use file size only to guess renames
  190.    * in the remaining files (could go wrong, but unlikely)
  191.    */
  192.   _processRenamedItems: function MoveRenameJob__processRenamedItems() {
  193.     this._total += this._mediaItems.length;
  194.  
  195.     // What is left in _mediaItems are either items that were removed, 
  196.     // or items that were renamed. 
  197.     var remainingItems = [];
  198.     for each (var item in this._mediaItems) {
  199.       this._progress++;
  200.       var size = item.getProperty(SBProperties.contentLength);
  201.       if (this._map[size]) {
  202.         var count = 0;
  203.         var name;
  204.         for (name in this._map[size]) count++;
  205.         if (count == 1 && this._map[size][name].length == 1) {
  206.           // If there is only one file name with this size, then 
  207.           // it is probably a rename
  208.           //LOG("found renamed item " + item.contentSrc.spec);
  209.           item.contentSrc = this._map[size][name].shift();
  210.         } else if (count == 0) {
  211.           // No remaining file names for this size. Ignore.
  212.         } else {
  213.           // Hmm, multiple files with the same size.
  214.           // We can't reliably rename, so just warn
  215.           dump("Watch Folders: unable to resolve ambiguous file renames\n");
  216.         }
  217.         delete this._map[size];
  218.       } else {
  219.         //LOG(item.contentSrc.spec + " was not renamed");
  220.         remainingItems.push(item);
  221.       }
  222.       
  223.       yield this._yieldIfShouldWithStatusUpdate();
  224.     }
  225.     
  226.     this._mediaItems = remainingItems;
  227.   },
  228.  
  229.   /**
  230.    * At this point we've done all we can do.  
  231.    * Whatever is left needs to be removed/added
  232.    */
  233.   _processRemaining: function MoveRenameJob__processRemaining() {
  234.     // Delete all the remaining media items.  We 
  235.     // weren't able to salvage them.
  236.     if (this._mediaItems.length > 0) {
  237.       var library = this._mediaItems[0].library;
  238.       //LOG("deleting " + this._mediaItems.length + " remaining items");
  239.       library.removeSome(ArrayConverter.enumerator(this._mediaItems));
  240.     }
  241.     
  242.     // Find all the new paths that weren't mapped old items
  243.     var newPaths = [];
  244.     for (var size in this._map) {
  245.       for (var name in this._map[size]) {
  246.         newPaths = newPaths.concat(this._map[size][name]);
  247.       }
  248.     }
  249.     this._map = null; 
  250.     
  251.     if (newPaths.length > 0) {
  252.       //LOG("adding " + newPaths.length + " new items");
  253.       // Add them as new media items
  254.       var importer = Cc['@songbirdnest.com/Songbird/DirectoryImportService;1']
  255.                        .getService(Ci.sbIDirectoryImportService);
  256.       var importJob = importer.import(ArrayConverter.nsIArray(newPaths));
  257.       this.delegateJobProgress(importJob);
  258.     }
  259.   },
  260.  
  261.   /**
  262.    * Yield the generator thread if needed, and 
  263.    * update the job progress
  264.    */
  265.   _yieldIfShouldWithStatusUpdate: 
  266.   function MoveRenameJob__yieldIfShouldWithStatusUpdate()  {
  267.     if (GeneratorThread.shouldYield())
  268.       yield this._yieldWithStatusUpdate();
  269.   },
  270.  
  271.   /**
  272.    * Update job progress
  273.    */
  274.   _yieldWithStatusUpdate: 
  275.   function MoveRenameJob_yieldIfShouldWithStatusUpdate()  {
  276.     this.notifyJobProgressListeners();
  277.     yield;
  278.   },
  279.  
  280.   /**
  281.    * Called when a sub-job (set via Job.delegateJobProgress) completes.
  282.    */
  283.   onJobDelegateCompleted: function MoveRenameJob_onJobDelegateCompleted() {
  284.  
  285.     // Track overall progress, so that the progress bar reflects
  286.     // the total number of items to process, not the individual 
  287.     // batches.
  288.     this._progress += this._jobProgressDelegate.progress;
  289.     
  290.     // Stop delegating
  291.     this.delegateJobProgress(null);
  292.     
  293.     this.complete();
  294.   },
  295.  
  296.   /**
  297.    * Override JobBase.progress, to make sure the metadata scan progress
  298.    * bar reflects the total item count, not the current batch
  299.    */
  300.   get progress() {
  301.     return (this._jobProgressDelegate) ? 
  302.       this._jobProgressDelegate.progress + this._progress : this._progress;
  303.   },
  304.   
  305.   get total() {
  306.     return this._total;
  307.   },
  308.   
  309.   /**
  310.    * Override JobBase.titleText, since we want to maintain
  311.    * the same title throughout the import process
  312.    */
  313.   get titleText() {
  314.     return this._titleText;
  315.   },
  316.  
  317.   /** sbIJobCancelable **/
  318.   cancel: function MoveRenameJob_cancel() {
  319.     if (!this.canCancel) {
  320.       throw new Error("MoveRenameJob not currently cancelable");
  321.     }
  322.  
  323.     if (this._thread) {
  324.       this._thread.terminate();
  325.       this._thread = null;
  326.     }
  327.     
  328.     if (this._jobProgressDelegate) {
  329.       // Cancelling the sub-job will trigger onJobDelegateCompleted
  330.       this._jobProgressDelegate.cancel();
  331.     } else {
  332.       this.complete();
  333.     }
  334.   },
  335.   
  336.   /**
  337.    * Stop everything, complete the job.
  338.    */
  339.   complete: function MoveRenameJob_complete() {
  340.         
  341.     this._status = Ci.sbIJobProgress.STATUS_SUCCEEDED;
  342.     this._statusText = SBString("watchfolders.moverename.complete"); 
  343.     this.notifyJobProgressListeners();
  344.  
  345.     // Remove the job listener from the super class.
  346.     this.removeJobProgressListener(this._listener);
  347.     this._listener = null;
  348.     
  349.     this._owner.onJobComplete();
  350.   }
  351. }
  352.  
  353.  
  354.  
  355.  
  356.  
  357.  
  358. /******************************************************************************
  359.  * Object implementing sbIWFMoveRenameHelper9000. Used to start a 
  360.  * new processing job.
  361.  *****************************************************************************/
  362. function MoveRenameHelper() {  
  363.   this._jobs = [];
  364. }
  365.  
  366. MoveRenameHelper.prototype = {
  367.   classDescription: "Songbird Watch Folder Move/Rename Helper Service",
  368.   classID:          Components.ID("{02ba1ba0-fee5-11dd-87af-0800200c9a66}"),
  369.   contractID:       "@songbirdnest.com/Songbird/MoveRenameHelper;1",
  370.   QueryInterface:   XPCOMUtils.generateQI([Ci.sbIWFMoveRenameHelper9000]),
  371.   
  372.   // List of pending jobs.  We only want to allow one to run at a time, 
  373.   // since this can lock up the UI.
  374.   _jobs: null,
  375.  
  376.   /**
  377.    * \brief Start a job for the given added/removed paths
  378.    */
  379.   process: function MoveRenameHelper_process(aItems, aPaths, aListener) {
  380.     dump("WatchFolders MoveRenameHelper_process\n");
  381.     var job = new MoveRenameJob(aItems, aPaths, this, aListener);
  382.     
  383.     this._jobs.push(job);
  384.     
  385.     // If this is the only job, just start immediately.
  386.     // Otherwise it will be started when the current job finishes.
  387.     if (this._jobs.length == 1) {
  388.  
  389.       // Make sure things QI correctly...
  390.       var sip = Cc["@mozilla.org/supports-interface-pointer;1"]
  391.                   .createInstance(Ci.nsISupportsInterfacePointer);
  392.       sip.data = job;
  393.       
  394.       // Wrap the job in a modal dialog and library batch
  395.       LibraryUtils.mainLibrary.runInBatchMode(function() {
  396.         job.begin();
  397.         SBJobUtils.showProgressDialog(sip.data, null, 100);
  398.       });
  399.     }
  400.   },
  401.   
  402.   /**
  403.    * \brief Called by the active job when it completes
  404.    */
  405.   onJobComplete: function MoveRenameHelper_onJobComplete() {
  406.     this._jobs.shift();
  407.     if (this._jobs.length > 0) {
  408.       this._jobs[0].begin();
  409.     }
  410.   }
  411.  
  412. } // MoveRenameHelper.prototype
  413.  
  414.  
  415. function NSGetModule(compMgr, fileSpec) {
  416.   return XPCOMUtils.generateModule([MoveRenameHelper]);
  417. }
  418.