sbDirectoryImportService.js
Go to the documentation of this file.
1 /*
2 //
3 // BEGIN SONGBIRD GPL
4 //
5 // This file is part of the Songbird web player.
6 //
7 // Copyright(c) 2005-2012 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/DebugUtils.jsm");
39 Cu.import("resource://app/jsmodules/URLUtils.jsm");
40 
45 const LOG = DebugUtils.generateLogFunction("sbDirectoryImportJob");
46 
47 // used to identify directory import profiling runs
48 var gCounter = 0;
49 
50 /******************************************************************************
51  * Object implementing sbIDirectoryImportJob, responsible for finding media
52  * items on disk, adding them to the library and performing a metadata scan.
53  *
54  * Call begin() to start the job.
55  *****************************************************************************/
56 function DirectoryImportJob(aInputArray,
57  aTypeSniffer,
58  aMetadataScanner,
59  aTargetMediaList,
60  aTargetIndex,
61  aImportService) {
62  if (!(aInputArray instanceof Ci.nsIArray &&
63  aInputArray.length > 0) ||
64  !(aTargetMediaList instanceof Ci.sbIMediaList)) {
65  throw Components.results.NS_ERROR_INVALID_ARG;
66  }
67  // Call super constructor
68  SBJobUtils.JobBase.call(this);
69 
70  this._inputFiles = ArrayConverter.JSArray(aInputArray);
71  this.targetMediaList = aTargetMediaList;
72  this.targetIndex = aTargetIndex;
73 
74  this._importService = aImportService;
75  this._typeSniffer = aTypeSniffer;
76  this._metadataScanner = aMetadataScanner;
77 
78  // TODO these strings probably need updating
79  this._titleText = SBString("media_scan.scanning");
80  this._statusText = SBString("media_scan.adding");
81 
82  // Initially cancelable
83  this._canCancel = true;
84 
85  // initialize with an empty array
86  this._itemURIStrings = [];
87 
88  this._libraryUtils = Cc["@songbirdnest.com/Songbird/library/Manager;1"]
89  .getService(Ci.sbILibraryUtils);
90 
91  if ("@songbirdnest.com/Songbird/TimingService;1" in Cc) {
92  this._timingService = Cc["@songbirdnest.com/Songbird/TimingService;1"]
93  .getService(Ci.sbITimingService);
94  this._timingIdentifier = "DirImport" + gCounter++;
95  }
96 }
97 DirectoryImportJob.prototype = {
98  __proto__: SBJobUtils.JobBase.prototype,
99 
100  QueryInterface: XPCOMUtils.generateQI(
101  [Ci.sbIDirectoryImportJob, Ci.sbIJobProgress, Ci.sbIJobProgressUI,
102  Ci.sbIJobProgressListener, Ci.sbIJobCancelable, Ci.nsIObserver,
103  Ci.nsIClassInfo]),
104 
106  getInterfaces: function(count) {
107  var interfaces = [Ci.sbIDirectoryImportJob, Ci.sbIJobProgress, Ci.sbIJobProgressListener,
108  Ci.sbIJobCancelable, Ci.nsIClassInfo, Ci.nsISupports];
109  count.value = interfaces.length;
110  return interfaces;
111  },
112 
113  totalAddedToMediaList : 0,
114  totalAddedToLibrary : 0,
115  totalDuplicates : 0,
116  targetMediaList : null,
117  targetIndex : null,
118 
119  // nsITimer used to poll the filescanner. *sigh*
120  _pollingTimer : null,
121  FILESCAN_POLL_INTERVAL : 33,
122 
123  // sbIFileScan used to find media files in directories
124  _fileScanner : null,
125  _fileScanQuery : null,
126 
127  // Array of nsIFile directory paths or files
128  _inputFiles : null,
129  _fileExtensions : null,
130  _flaggedFileExtensions : null,
131  _foundFlaggedExtensions : null,
132 
133  // The sbIDirectoryImportService. Called back on job completion.
134  _importService : null,
135 
136  _typeSniffer : null,
137 
138  _metadataScanner : null,
139 
140  // JS Array of URI strings for all found media items
141  _itemURIStrings : [],
142 
143  // Optional JS array of items found to already exist in the main library
144  _itemsInMainLib : [],
145  // Rather than create all the items in one pass, then scan them all,
146  // we want to create, read, repeat with small batches. This avoids
147  // wasting/fragmenting memory when importing 10,000+ tracks.
148  // TODO tweak
149  BATCHCREATE_SIZE : 300,
150 
151  // Index into _itemURIStrings for the beginning of the
152  // next create/read/add batch
153  _nextURIIndex : 0,
154 
155  // Temporary nsIArray of previously unknown sbIMediaItems,
156  // used to pass newly created items to a metadata scan job.
157  // Set in _onItemCreation.
158  _currentMediaItems : null,
159  // The size of the current batch
160  _currentBatchSize : 0,
161 
162  // True if we've forced the library into a batch state for
163  // performance reasons
164  _inLibraryBatch : false,
165 
166  // sbILibraryUtils used to produce content URI's
167  _libraryUtils : null,
168 
169  // Used to track performance
170  _timingService : null,
171  _timingIdentifier : null,
172 
178  enumerateAllItems: function DirectoryImportJob_enumerateAllItems() {
179  // Ultimately the batch create job would give us this list,
180  // but at the moment we have to recreate it ourselves
181  // using the list of URIs from the file scan.
182 
183  // If no URIs, just return the empty enumerator
184  if (this._itemURIStrings.length == 0) {
185  return ArrayConverter.enumerator([]);
186  }
187 
188  // If all the URIs resulted in new items, and
189  // were processed in a single batch, then we can
190  // just return the current items.
191  if (this._currentMediaItems &&
192  this._itemURIStrings.length == this._currentMediaItems.length) {
193  return this._currentMediaItems.enumerate();
194  }
195 
196 
197  // Otherwise, we'll need to get media items for all the
198  // URIs that have been added. We want to avoid instantiating
199  // all the items at once (since there may be hundreds of thousands),
200  // so instead get a few at a time.
201 
202  var uriEnumerator = ArrayConverter.enumerator(this._itemURIStrings);
203  var library = this.targetMediaList.library;
204  const BATCHSIZE = this.BATCHCREATE_SIZE;
205 
206  // This enumerator instantiates the new media items on demand.
207  var enumerator = {
208  mediaItems: [],
209 
210  // sbIMediaListEnumerationListener, used to fetch items
211  onEnumerationBegin: function() {},
212  onEnumeratedItem: function(list, item) {
213  this.mediaItems.push(item);
214  },
215  onEnumerationEnd: function() {},
216 
217  // nsISimpleEnumerator, used to dispense items
218  hasMoreElements: function() {
219  return (this.mediaItems.length ||
220  uriEnumerator.hasMoreElements());
221  },
222  getNext: function() {
223  // When the buffer runs out, fetch more items from the library
224  if (this.mediaItems.length == 0) {
225  // Get the next set of URIs
226  var propertyArray = SBProperties.createArray();
227  var counter = 0;
228  while (uriEnumerator.hasMoreElements() && counter < BATCHSIZE) {
229  var itemURIStr = uriEnumerator.getNext().QueryInterface(Ci.nsISupportsString);
230  propertyArray.appendProperty(SBProperties.contentURL, itemURIStr.data);
231  counter++;
232  }
233  library.enumerateItemsByProperties(propertyArray, this);
234  }
235 
236  return this.mediaItems.shift();
237  },
238 
239  QueryInterface: XPCOMUtils.generateQI([
240  Ci.nsISimpleEnumerator, Ci.sbIMediaListEnumerationListener]),
241  };
242 
243  return enumerator;
244  },
245 
246 
250  begin: function DirectoryImportJob_begin() {
251  if (this._timingService) {
252  this._timingService.startPerfTimer(this._timingIdentifier);
253  }
254 
255  // Start by finding all the files in the given directories
256 
257  var Application = Cc["@mozilla.org/fuel/application;1"]
258  .getService(Ci.fuelIApplication);
259 
260  this._fileScanner = Cc["@songbirdnest.com/Songbird/FileScan;1"]
261  .createInstance(Components.interfaces.sbIFileScan);
262 
263  // Figure out what files we are looking for.
264  try {
265  var extensions = this._typeSniffer.mediaFileExtensions;
266  if (!Application.prefs.getValue("songbird.mediascan.enableVideoImporting", true)) {
267  // disable video, so scan only audio - see bug 13173
268  extensions = this._typeSniffer.audioFileExtensions;
269  }
270  this._fileExtensions = [];
271  while (extensions.hasMore()) {
272  this._fileExtensions.push(extensions.getNext());
273  }
274  } catch (e) {
275  dump("WARNING: DirectoryImportJob_begin could not find supported file extensions. " +
276  "Assuming test mode, and using a hardcoded list.\n");
277  this._fileExtensions = ["mp3", "ogg", "flac"];
278  }
279 
280  // Add the unsupported file extensions as flagged extensions to the file
281  // scanner so that the user can be notified if any unsupported extensions
282  // were discovered.
283  //
284  // NOTE: if the user choose to ignore import warnings, do not bother adding
285  // the filter list here.
286  var shouldWarnFlagExtensions = Application.prefs.getValue(
287  "songbird.mediaimport.warn_filtered_exts", true);
288  if (shouldWarnFlagExtensions) {
289  this._foundFlaggedExtensions =
290  Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
291  .createInstance(Ci.nsIMutableArray);
292  this._flaggedFileExtensions = [];
293  try {
294  var unsupportedExtensions = this._typeSniffer.unsupportedVideoFileExtensions;
295  while (unsupportedExtensions.hasMore()) {
296  var item = unsupportedExtensions.getNext();
297  this._flaggedFileExtensions.push(item);
298  }
299  }
300  catch (e) {
301  Components.utils.reportError(
302  e + "\nCould not add unsupported file extensions to the file scan!");
303  }
304  }
305 
306  // XXX If possible, wrap the entire operation in an update batch
307  // so that onbatchend listeners dont go to work between the
308  // end of the batchcreate and the start of the metadata scan.
309  // This is an ugly hack, but it prevents a few second hang
310  // when importing 100k tracks.
311  var library = this.targetMediaList.library;
312  if (library instanceof Ci.sbILocalDatabaseLibrary) {
313  this._inLibraryBatch = true;
314  library.forceBeginUpdateBatch();
315  }
316 
317  this._startNextDirectoryScan();
318 
319  // Now poll the file scan, since it apparently perf is much better this way
320  this._pollingTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
321  this._pollingTimer.init(this, this.FILESCAN_POLL_INTERVAL, Ci.nsITimer.TYPE_REPEATING_SLACK);
322  },
323 
328  _startNextDirectoryScan: function DirectoryImportJob__startNextDirectoryScan() {
329  // Just report the error rather then throwing, since this method
330  // is called by the timer.
331  if (this._fileScanQuery && this._fileScanQuery.isScanning()) {
332  Cu.reportError(
333  "DirectoryImportJob__startDirectoryScan called with a scan in progress?");
334  return;
335  }
336 
337  var file = this._inputFiles.shift(); // Process directories in the order provided
338 
339  if (file && file instanceof Ci.nsIFileURL) {
340  file = file.file;
341  }
342 
343  // If something is messed up, just report and wait. The poll function will
344  // move on to the next step.
345  if (!file || !(file instanceof Ci.nsIFile)) {
346  Cu.reportError("DirectoryImportJob__startNextDirectoryScan: invalid directory");
347  return;
348  }
349 
350  // We use the same _fileScanQuery object for scanning multiple directories.
351  if (!this._fileScanQuery) {
352  this._fileScanQuery = Cc["@songbirdnest.com/Songbird/FileScanQuery;1"]
353  .createInstance(Components.interfaces.sbIFileScanQuery);
354  var fileScanQuery = this._fileScanQuery;
355  this._fileExtensions.forEach(function(ext) { fileScanQuery.addFileExtension(ext) });
356 
357  // Assign the unsupported file extensions as flagged extensions in the scanner.
358  if (this._flaggedFileExtensions) {
359  this._flaggedFileExtensions.forEach(function(ext) {
360  fileScanQuery.addFlaggedFileExtension(ext);
361  });
362  }
363  }
364 
365  if (file.exists() && file.isDirectory()) {
366  this._fileScanQuery.setDirectory(file.path);
367  this._fileScanQuery.setRecurse(true);
368  this._fileScanner.submitQuery(this._fileScanQuery);
369  } else {
370  var urispec = this._libraryUtils.getFileContentURI(file).spec;
371 
372  // Ensure that the file extension is not blacklisted.
373  var isValidExt = false;
374  var dotIndex = urispec.lastIndexOf(".");
375  if (dotIndex > -1) {
376  var fileExt = urispec.substr(dotIndex + 1);
377  for (var i = 0; i < this._fileExtensions.length; i++) {
378  if (this._fileExtensions[i] == fileExt) {
379  isValidExt = true;
380  break;
381  }
382  }
383  }
384 
385  if (isValidExt) {
386  var supportsString = Cc["@mozilla.org/supports-string;1"]
387  .createInstance(Ci.nsISupportsString);
388  supportsString.data = urispec;
389  this._itemURIStrings.push(supportsString);
390  }
391  }
392  },
393 
398  _onPollFileScan: function DirectoryImportJob__onPollFileScan() {
399 
400  // If the current query is finished, collect the results and move on to
401  // the next directory
402  if (!this._fileScanQuery.isScanning()) {
403  // If there are more directories to scan, start the next one
404  if (this._inputFiles.length > 0) {
405  this._startNextDirectoryScan();
406  } else {
407  // Otherwise, we're done scanning directories, and it is time to collect
408  // information and create media items
409  var fileCount = this._fileScanQuery.getFileCount();
410  if (fileCount > 0 ) {
411  var strings = this._fileScanQuery.getResultRangeAsURIStrings(0, fileCount - 1);
412  Array.prototype.push.apply(this._itemURIStrings, ArrayConverter.JSArray(strings));
413  }
414  this._finishFileScan();
415  this._startMediaItemCreation();
416  }
417  // If the file scan query is still running just update the UI
418  } else {
419  var text = this._fileScanQuery.getLastFileFound();
420  text = decodeURIComponent(text.split("/").pop());
421  if (text.length > 60) {
422  text = text.substring(0, 10) + "..." + text.substring(text.length - 40);
423  }
424  this._statusText = text;
425  this.notifyJobProgressListeners();
426  }
427  },
428 
433  _finishFileScan: function DirectoryImportJob__finishFileScan() {
434  if (this._fileScanQuery.flaggedExtensionsFound) {
435  var ioService = Cc["@mozilla.org/network/io-service;1"]
436  .getService(Ci.nsIIOService);
437  // Add the leaf name of any flagged file paths to show the user at the
438  // end of the import. See bug 19553.
439  var flaggedExtCount = this._fileScanQuery.getFlaggedFileCount();
440  for (var i = 0; i < flaggedExtCount; i++) {
441  var curFlaggedPath = this._fileScanQuery.getFlaggedFilePath(i);
442  var curFlaggedURL = ioService.newURI(curFlaggedPath, null, null);
443  if (curFlaggedURL instanceof Ci.nsIFileURL) {
444  var curFlaggedFile = curFlaggedURL.QueryInterface(Ci.nsIFileURL).file;
445  var curSupportsStr = Cc["@mozilla.org/supports-string;1"]
446  .createInstance(Ci.nsISupportsString);
447  curSupportsStr.data = curFlaggedFile.leafName;
448 
449  this._foundFlaggedExtensions.appendElement(curSupportsStr, false);
450  }
451  }
452  }
453 
454  if (this._fileScanner) {
455  this._fileScanner.finalize();
456  this._fileScanner = null;
457  }
458  if (this._pollingTimer) {
459  this._pollingTimer.cancel();
460  this._pollingTimer = null;
461  }
462  // Set total number of items to process
463  this._total = this._itemURIStrings.length;
464  },
465 
476  _filterItems: function(aURIs, aItemsInMainLib) {
477  // If we're copying to another library we need to look in the main library
478  // for items that might have the origin URL the same as our URL's.
479  var targetLib = this.targetMediaList.library;
480  var mainLib = LibraryUtils.mainLibrary;
481  var isMainLib = this.targetMediaList.library.equals(mainLib);
482 
491  function filterDupes(uri)
492  {
493  var uriObj = uri.QueryInterface(Ci.nsISupportsString);
494  var uriSpec = uriObj.data;
495 
496  LOG("Searching for URI: " + uriSpec);
497 
498  // If we're importing to a non-main library the see if it exists in
499  // the main library
500  if (!isMainLib) {
501  var items = [];
502  try {
503  // If we find it in the main library then save the item off and
504  // filter it out of the array
505  items = ArrayConverter.JSArray(
506  mainLib.getItemsByProperty(SBProperties.contentURL,
507  uriSpec));
508  }
509  catch (e) {
510  LOG("Exception: " + e);
511  // Exception expected if nothing is found. Just continue
512  }
513  if (items.length > 0) {
514  LOG(" Found in main library");
515  aItemsInMainLib.push(items[0].QueryInterface(Ci.sbIMediaItem));
516  return false;
517  }
518 
519  }
520  var items = [];
521  try
522  {
523  // If we find the item by origin URL in the target library then
524  // bump the dupe count and filter it out of the array
525  items = ArrayConverter.JSArray(
526  targetLib.getItemsByProperty(SBProperties.originURL,
527  uriSpec));
528  }
529  catch (e) {
530  LOG("Exception: " + e);
531  }
532  if (items.length > 0) {
533  LOG(" Found in target library");
534  ++this.totalDuplicates;
535  return false;
536  }
537  LOG(" Item needs to be created");
538  // Item wasn't found so leave it in the array
539  return true;
540  }
541 
542  return aURIs.filter(filterDupes);
543  },
549  _startMediaItemCreation:
550  function DirectoryImportJob__startMediaItemCreation() {
551  if (!this._fileScanQuery) {
552  Cu.reportError(
553  "DirectoryImportJob__startMediaItemCreation called with invalid state");
554  this.complete();
555  return;
556  }
557 
558  var targetLib = this.targetMediaList.library;
559 
560  if (this._nextURIIndex >= this._itemURIStrings.length &&
561  this._itemsInMainLib.length === 0) {
562  LOG("Finish creating and adding all items");
563  this.complete();
564  return;
565  }
566  // For the items we found in the main library add them to the target library
567  else if (this._itemsInMainLib.length) {
568  LOG("Finish creating all items, now adding items");
569  // Setup listener object so we can mark the items as
570  var self = this;
571  var addSomeListener = {
572  onProgress: function(aItemsProcessed, aCompleted) {},
573  onItemAdded: function(aMediaItem) {
574  aMediaItem.setProperty(SBProperties.originIsInMainLibrary, "1");
575  },
576  onComplete: function() {
577  LOG("Adding items completed");
578  self._itemsInMainLib = [];
579  self.complete();
580  }
581  }
582  // Now process items we found in the main library
583  targetLib.addMediaItems(ArrayConverter.enumerator(this._itemsInMainLib),
584  addSomeListener,
585  true);
586  return;
587  }
588  // Update status
589  this._statusText = SBString("media_scan.adding");
590  this.notifyJobProgressListeners();
591 
592  // Process the URIs a slice at a time, since creating all
593  // of them at once may require a very large amount of memory.
594  this._currentBatchSize = Math.min(this.BATCHCREATE_SIZE,
595  this._itemURIStrings.length - this._nextURIIndex);
596  var endIndex = this._nextURIIndex + this._currentBatchSize;
597  var uris = this._itemURIStrings.slice(this._nextURIIndex, endIndex);
598  this._nextURIIndex = endIndex;
599 
600  LOG("Creating media items");
601 
602  this._itemsInMainLib = [];
603  LOG("Total items=" + uris.length);
604  uris = this._filterItems(uris, this._itemsInMainLib);
605 
606  LOG("Items in main library=" + this._itemsInMainLib.length);
607  LOG("Items needing to be created=" + uris.length);
608 
609  // Now that the uri list is settled, make a corresponding list of media
610  // item property arrays. For now, we just ask the type sniffer whether
611  // the media item contentType should be image. If you try to do this in
612  // another component, you'll probably find that you don't have access to
613  // just any ol' type sniffer that might be used here, only the default
614  // mediacore type sniffer.
615  const NO_PROPS = SBProperties.createArray();
616  const IMAGE_PROPS = function () {
617  var props = SBProperties.createArray();
618  props.appendProperty(SBProperties.contentType, "image");
619  return props;
620  } ();
621  var propsArray = uris.map(function (aURISpec) {
622  var uri = URLUtils.newURI(aURISpec);
623  if (this._typeSniffer.isValidImageURL(uri)) {
624  return IMAGE_PROPS;
625  }
626  return NO_PROPS;
627  }, this);
628 
629  // Bug 10228 - this needs to be replaced with an sbIJobProgress interface
630  var thisJob = this;
631  var batchCreateListener = {
632  onProgress: function(aIndex) {},
633  onComplete: function(aMediaItems, aResult) {
634  LOG("Finished creating batch of items");
635  thisJob._onItemCreation(aMediaItems, aResult, uris.length);
636  }
637  };
638 
639  // Create items that weren't in the main library or already in the target
640  // library
641  targetLib.batchCreateMediaItemsAsync(batchCreateListener,
642  ArrayConverter.nsIArray(uris),
643  ArrayConverter.nsIArray(propsArray),
644  false);
645  },
646 
652  _onItemCreation: function DirectoryImportJob__onItemCreation(aMediaItems,
653  aResult,
654  itemsToCreateCount) {
655  // Get the completed item array. Don't use the given item array on error.
656  // Use an empty one instead.
657  LOG("batchCreateMediaItemsAsync created " + aMediaItems.length)
658  if (Components.isSuccessCode(aResult)) {
659  this.totalDuplicates += (itemsToCreateCount - aMediaItems.length);
660  this._currentMediaItems = aMediaItems;
661  } else {
662  Cu.reportError("DirectoryImportJob__onItemCreation: aResult == " + aResult);
663  this._currentMediaItems = Components.classes["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
664  .createInstance(Components.interfaces.nsIArray);
665  }
666 
667  this.totalAddedToLibrary += this._currentMediaItems.length;
668  if (this._currentMediaItems.length > 0) {
669 
670  // Make sure we have metadata for all the added items
671  this._startMetadataScan();
672  } else {
673  // no items were created, probably because they were all old.
674  // try the next batch.
675  this._startMediaItemCreation();
676  }
677  },
678 
683  _startMetadataScan:
684  function DirectoryImportJob__startMetadataScan() {
685  if (this._currentMediaItems && this._currentMediaItems.length > 0) {
686 
687  var metadataJob = this._metadataScanner.read(this._currentMediaItems);
688 
689  // Pump metadata job progress to the UI
690  this.delegateJobProgress(metadataJob);
691  } else {
692  // Nothing to do.
693  this.complete();
694  }
695  },
696 
700  _insertFoundItemsIntoTarget:
701  function DirectoryImportJob__insertFoundItemsIntoTarget() {
702  // If we are inserting into a list, there is more to do than just importing
703  // the tracks into its library, we also need to insert all the items (even
704  // the ones that previously existed) into the list at the requested position
705  if (!(this.targetMediaList instanceof Ci.sbILibrary)) {
706  try {
707  if (this._itemURIStrings.length > 0) {
708  var originalLength = this.targetMediaList.length;
709  // If we need to insert, then do so
710  if ((this.targetMediaList instanceof Ci.sbIOrderableMediaList) &&
711  (this.targetIndex >= 0) &&
712  (this.targetIndex < this.targetMediaList.length)) {
713  this.targetMediaList.insertSomeBefore(this.targetIndex, this.enumerateAllItems());
714  } else {
715  // Otherwise, just add
716  this.targetMediaList.addSome(this.enumerateAllItems());
717  }
718  this.totalAddedToMediaList = this.targetMediaList.length - originalLength;
719  }
720  } catch (e) {
721  Cu.reportError(e);
722  }
723  }
724  },
725 
729  onJobDelegateCompleted: function DirectoryImportJob_onJobDelegateCompleted() {
730 
731  // Track overall progress, so that the progress bar reflects
732  // the total number of items to process, not the individual
733  // batches.
734  this._progress += this._jobProgressDelegate.progress;
735 
736  // Stop delegating
737  this.delegateJobProgress(null);
738 
739  // For now the only job we delegate to is metadata... so when
740  // it completes we go back to create the next set of media items.
741  this._startMediaItemCreation();
742  },
743 
748  get progress() {
749  return (this._jobProgressDelegate) ?
750  this._jobProgressDelegate.progress + this._progress : this._progress;
751  },
752 
753  get total() {
754  return this._total;
755  },
756 
761  get titleText() {
762  return this._titleText;
763  },
764 
766  cancel: function DirectoryImportJob_cancel() {
767  if (!this.canCancel) {
768  throw new Error("DirectoryImportJob not currently cancelable")
769  }
770 
771  if (this._fileScanner) {
772  this._finishFileScan();
773  this.complete();
774  } else if (this._jobProgressDelegate) {
775  // Cancelling the sub-job will trigger onJobDelegateCompleted
776  this._jobProgressDelegate.cancel();
777  }
778 
779  if (this._fileScanQuery) {
780  this._fileScanQuery.cancel();
781  this._fileScanQuery = null;
782  }
783 
784  // Remove anything that we've only partially processed
785  if (this._currentMediaItems && this._currentMediaItems.length > 0) {
786  this.targetMediaList.library.removeSome(this._currentMediaItems.enumerate());
787  this.totalAddedToMediaList = 0;
788  this.totalAddedToLibrary = 0;
789  this.totalDuplicates = 0;
790  }
791  },
792 
796  complete: function DirectoryImportJob_complete() {
797 
798  // Handle inserting into a media list at a specific index
799  this._insertFoundItemsIntoTarget();
800 
801  this._status = Ci.sbIJobProgress.STATUS_SUCCEEDED;
802  this._statusText = SBString("media_scan.complete");
803  this.notifyJobProgressListeners();
804 
805  this._importService.onJobComplete();
806 
807  // XXX If we forced the library into a batch mode
808  // in order to improve performance, make sure
809  // we end the batch (if we fail to do this
810  // the tree view will never update)
811  var library = this.targetMediaList.library;
812  if (library instanceof Ci.sbILocalDatabaseLibrary &&
813  this._inLibraryBatch)
814  {
815  this._inLibraryBatch = false;
816  library.forceEndUpdateBatch();
817 
818  // XXX Performance hack
819  // If we've imported a ton of items then most of the
820  // library database is probably in memory.
821  // This isn't useful, and makes a bad first impression,
822  // so lets just dump the entire DB cache.
823  if (this.totalAddedToLibrary > this.BATCHCREATE_SIZE) {
824  // More performance hackery. We run optimize with the ANALYZE step.
825  // This will ensure that all queries can use the best possible indexes.
826  library.optimize(true);
827 
828  // Analyze will load a bunch of stuff into memory so we want to release
829  // after analyze completes.
830  var dbEngine = Cc["@songbirdnest.com/Songbird/DatabaseEngine;1"]
831  .getService(Ci.sbIDatabaseEngine);
832  dbEngine.releaseMemory();
833  }
834  }
835 
836  if (this._fileScanQuery) {
837  this._fileScanQuery.cancel();
838  this._fileScanQuery = null;
839  }
840 
841  if (this._timingService) {
842  this._timingService.stopPerfTimer(this._timingIdentifier);
843  }
844 
845  // If flagged extensions were found, show the dialog.
846  if (this._foundFlaggedExtensions &&
847  this._foundFlaggedExtensions.length > 0)
848  {
849  var winMed = Cc["@mozilla.org/appshell/window-mediator;1"]
850  .getService(Ci.nsIWindowMediator);
851  var sbWin = winMed.getMostRecentWindow("Songbird:Main");
852 
853  var prompter = Cc["@songbirdnest.com/Songbird/Prompter;1"]
854  .getService(Ci.sbIPrompter);
855  prompter.waitForWindow = false;
856 
857  var dialogBlock = Cc["@mozilla.org/embedcomp/dialogparam;1"]
858  .createInstance(Ci.nsIDialogParamBlock);
859 
860  // Assign the flagged files
861  dialogBlock.objects = this._foundFlaggedExtensions;
862 
863  // Now open the dialog.
864  prompter.openDialog(sbWin,
865  "chrome://songbird/content/xul/mediaimportWarningDialog.xul",
866  "mediaimportWarningDialog",
867  "chrome,centerscreen,modal=yes",
868  dialogBlock);
869  }
870  },
871 
875  observe: function DirectoryImportJob_observe(aSubject, aTopic, aData) {
876  this._onPollFileScan();
877  },
878 
882  crop: "center"
883 }
884 
885 
886 
887 
888 
889 
890 /******************************************************************************
891  * Object implementing sbIDirectoryImportService. Used to start a
892  * new media import job.
893  *****************************************************************************/
894 function DirectoryImportService() {
895  this._importJobs = [];
896 }
897 
898 DirectoryImportService.prototype = {
899  classDescription: "Songbird Directory Import Service",
900  classID: Components.ID("{6e542f90-44a0-11dd-ae16-0800200c9a66}"),
901  contractID: "@songbirdnest.com/Songbird/DirectoryImportService;1",
902  QueryInterface: XPCOMUtils.generateQI([Ci.sbIDirectoryImportService]),
903 
904  // List of pending jobs. We only want to allow one to run at a time,
905  // since this can lock up the UI.
906  _importJobs: null,
907 
912  import: function DirectoryImportService_import(aDirectoryArray, aTargetMediaList, aTargetIndex) {
913  return this.importWithCustomSnifferAndMetadataScanner(
914  aDirectoryArray, null, null, aTargetMediaList, aTargetIndex);
915  },
916 
917  importWithCustomSnifferAndMetadataScanner: function DirectoryImportService_importWithCustomSniffer(
918  aDirectoryArray, aTypeSniffer, aMetadataScanner, aTargetMediaList, aTargetIndex) {
919  if (!aTypeSniffer) {
920  aTypeSniffer = Cc["@songbirdnest.com/Songbird/Mediacore/TypeSniffer;1"]
921  .createInstance(Ci.sbIMediacoreTypeSniffer);
922  }
923  if (!aMetadataScanner) {
924  aMetadataScanner = Cc["@songbirdnest.com/Songbird/FileMetadataService;1"]
925  .getService(Ci.sbIFileMetadataService);
926  }
927  // Default to main library if not target is provided
928  if (!aTargetMediaList) {
929  aTargetMediaList = LibraryUtils.mainLibrary;
930  }
931 
932  var job = new DirectoryImportJob(
933  aDirectoryArray, aTypeSniffer, aMetadataScanner, aTargetMediaList, aTargetIndex, this);
934 
935  this._importJobs.push(job);
936 
937  // If this is the only job, just start immediately.
938  // Otherwise it will be started when the current job finishes.
939  if (this._importJobs.length == 1) {
940  job.begin();
941  }
942 
943  return job;
944  },
945 
949  onJobComplete: function DirectoryImportService_onJobComplete() {
950  this._importJobs.shift();
951  if (this._importJobs.length > 0) {
952  this._importJobs[0].begin();
953  }
954  }
955 
956 } // DirectoryImportService.prototype
957 
958 
959 function NSGetModule(compMgr, fileSpec) {
960  return XPCOMUtils.generateModule([DirectoryImportService]);
961 }
var total
var Application
Definition: sbAboutDRM.js:37
var uris
function onComplete(job)
Definition: test_bug7406.js:85
function DirectoryImportJob(aInputArray, aTypeSniffer, aMetadataScanner, aTargetMediaList, aTargetIndex, aImportService)
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
var ioService
function SBString(aKey, aDefault, aStringBundle)
Definition: StringUtils.jsm:93
var strings
Definition: Info.js:46
var count
Definition: test_bug7406.js:32
return null
Definition: FeedWriter.js:1143
SimpleArrayEnumerator prototype hasMoreElements
var uri
Definition: FeedWriter.js:1135
if(DEBUG_DATAREMOTES)
sbDeviceFirmwareAutoCheckForUpdate prototype classID
Javascript wrappers for common library tasks.
sbDeviceFirmwareAutoCheckForUpdate prototype getInterfaces
sbDeviceFirmwareAutoCheckForUpdate prototype interfaces
_getSelectedPageStyle s i
_updateTextAndScrollDataForFrame aData
var file
sbDeviceFirmwareAutoCheckForUpdate prototype observe