sbMoveRenameHelper.js
Go to the documentation of this file.
1 
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 
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 
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 
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 
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 
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 
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 
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 
265  _yieldIfShouldWithStatusUpdate:
266  function MoveRenameJob__yieldIfShouldWithStatusUpdate() {
267  if (GeneratorThread.shouldYield())
268  yield this._yieldWithStatusUpdate();
269  },
270 
274  _yieldWithStatusUpdate:
275  function MoveRenameJob_yieldIfShouldWithStatusUpdate() {
276  this.notifyJobProgressListeners();
277  yield;
278  },
279 
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 
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 
313  get titleText() {
314  return this._titleText;
315  },
316 
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 
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 
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 
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 }
var total
const Cr
function GeneratorThread(aEntryPoint)
const Ci
const Cu
function MoveRenameJob(aItems, aPaths, aService, aListener)
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
function SBString(aKey, aDefault, aStringBundle)
Definition: StringUtils.jsm:93
var count
Definition: test_bug7406.js:32
const Cc
return null
Definition: FeedWriter.js:1143
sbDeviceFirmwareAutoCheckForUpdate prototype classID
Javascript wrappers for common library tasks.
sbDeviceFirmwareAutoCheckForUpdate prototype getInterfaces
sbDeviceFirmwareAutoCheckForUpdate prototype interfaces
const Ce
function LOG(aMessage)
var file