sync.js
Go to the documentation of this file.
1 /* sync.js
2  * This will be loaded into the main window, and manage all Sync-related
3  * tasks.
4  */
5 // Make a namespace.
6 if (typeof foldersync == 'undefined') {
7  var foldersync = {};
8 };
9 
10 /* Sync controller
11  * Controller to manage sync queue and to perform queued syncs. Sends messages
12  * to UI listeners, and provides Sync capabilities.
13  */
14 foldersync.sync={
15  // The queued SyncQueueObjects (see generateSyncQueueObject)
16  _queue: null,
17  // The registered listeners (array of function(state,syncqueueobject))
18  _listeners: null,
19 
20  // All threads and thread-related stuff should stay here
21  _threads: {
22  // The update thread is responsible for calling listeners
23  update: {
24  // The message that will be sent soon.
25  crt: {
26  state: null,
27  syncqueueobject: null
28  },
29 
30  // The Thread's code
31  run: function(){
32  try {
33  foldersync.central.logEvent("sync-update",
34  "Update listeners", 5);
35  foldersync.sync._notify(this.crt.state, this.crt.syncqueueobject);
36  // Reset crt data
37  this.crt.state = null;
38  this.crt.syncqueueobject = null;
39  } catch(e) {
40  foldersync.central.logEvent("sync-update",
41  "Update thread terminated " +
42  "unexpected:\n\n" + e, 1,
43  "chrome://foldersync/content/sync.js",
44  e.lineNumber);
45  }
46  },
47  },
48  // The getQueue thread is responsible for getting SyncQueueObjects
49  getQueue: {
50  // The fetched SyncQueueObject, or null if there is no Object to fetch
51  crt: null,
52 
53  // The Thread's code
54  run: function(){
55  try {
56  foldersync.central.logEvent("sync-getQueue",
57  "Get element from Queue", 5);
58  // Get the next item
59  this.crt = null;
60  if (foldersync.sync._queue.length)
61  this.crt = foldersync.sync._queue.shift();
62  } catch(e) {
63  foldersync.central.logEvent("sync-getQueue",
64  "GetQueue thread terminated " +
65  "unexpected:\n\n" + e, 1,
66  "chrome://foldersync/content/sync.js",
67  e.lineNumber);
68  }
69  },
70  },
71  // The getProfile thread is responsible for getting Profiles
72  getProfile: {
73  // The GUID of the Profile to fetch, the fetched Profile, or null
74  // The profile has a additional sub-class "defaulttags".
75  crt: null,
76 
77  // The Thread's code
78  run: function(){
79  try {
80  foldersync.central.logEvent("sync-getProfile",
81  "Get Profile " + this.crt, 5);
82  // Get the profile
83  this.crt = foldersync.preferences.getProfileByGUID(this.crt);
84  // Make sure this.crt is NOT a reference
85  // This might be not that nice, but it works. If somebody has a
86  // better solution, feel free to contact me (rsjtdrjgfuzkfg).
87  this.crt = JSON.parse(JSON.stringify(this.crt));
88 
89  // Get default tags and append them (include to the profile)
90  var defTags = foldersync.preferences.getDefaultTags();
91  defTags = JSON.parse(JSON.stringify(defTags));
92  this.crt.defaulttags = defTags;
93 
94  // Get the fallbacks and append them (include to the profile)
95  var fallbacks = foldersync.preferences.getFallbacks();
96  fallbacks = JSON.parse(JSON.stringify(fallbacks));
97  this.crt.fallbacks = fallbacks;
98  } catch(e) {
99  foldersync.central.logEvent("sync-getProfile",
100  "GetProfile thread terminated " +
101  "unexpected:\n\n" + e, 1,
102  "chrome://foldersync/content/sync.js",
103  e.lineNumber);
104  }
105  },
106  },
107  // The getItems thread is responsible for getting items from playlists
108  getItems: {
109  /* The Array of playlists we want to fetch, the result or null
110  * Result structure: Array of playlists, each:
111  * {
112  * name: <string>, // the playlist's name (given by user)
113  * items: [], // all items the playlists has.
114  * }
115  * a mediaitem has the following structure:
116  * {
117  * tags: {}, // the tags, see the code in getProfile.run for details
118  * path: <string>, // the location of the connected file
119  * }
120  */
121  crt: null,
122 
123  // The Thread's code
124  run: function(){
125  try {
126  foldersync.central.logEvent("sync-getItems",
127  "Get Items: " + this.crt, 5);
128  // Make sure Modules we'll use are loaded
129  if (!window.SBProperties)
130  Components.utils.
131  import("resource://app/jsmodules/sbProperties.jsm");
132  // Make services avaiable by shorthand
133  var ios = Components.classes["@mozilla.org/network/io-service;1"].
134  getService(Components.interfaces.nsIIOService);
135  // Prepare result array
136  var playlists = this.crt;
137  this.crt = [];
138  // Go through all playlists
139  for each (var guid in playlists){
140  var pList = foldersync.central.getPlaylistByGUID(guid);
141  foldersync.central.logEvent("sync-getItems",
142  "Parse playlist " + pList.guid + "\n" +
143  "Name: " + pList.name, 5);
144  // Prepare the playlist's entry
145  var plEntry = {name: pList.name, items: []};
146  // Go through all items
147  for (var i = 0; i < pList.length; i++){
148  // Get the item
149  var item = pList.getItemByIndex(i);
150  // Get the item's path, if any
151  var path = null;
152  try {
153  path = ios.newURI(decodeURI(item.getProperty(
154  SBProperties.contentURL)), null, null).
155  QueryInterface(Components.interfaces.nsIFileURL).
156  file.path;
157  } catch (e){}
158  // If the item has got a path
159  if (path){
160  foldersync.central.logEvent("sync-getItems",
161  "Add item " + item.guid + "\n" +
162  "Path: " + path, 5);
163  // Prepare the item's entry
164  var entry = {
165  path: path
166  };
167  // Add tags we might need to the entry
168  entry.tags = {
169  artist: item.getProperty(SBProperties.artistName),
170  title: item.getProperty(SBProperties.trackName),
171  album: item.getProperty(SBProperties.albumName),
172  genre: item.getProperty(SBProperties.genre),
173  rating: item.getProperty(SBProperties.rating),
174  tracknum: item.getProperty(SBProperties.trackNumber),
175  albumartist: item.getProperty(SBProperties.albumArtistName),
176  year: item.getProperty(SBProperties.year),
177  discnumber: item.getProperty(SBProperties.discNumber),
178  disccount: item.getProperty(SBProperties.totalDiscs),
179  producer: item.getProperty(SBProperties.producerName),
180  composer: item.getProperty(SBProperties.composerName),
181  duration: item.getProperty(SBProperties.duration),
182  coverpath: null, // we'll fill that soon, see next lines
183  };
184  // Get album cover path, if any
185  try {
186  entry.tags.coverpath = ios.newURI(decodeURI(item.getProperty(
187  "http://songbirdnest.com/data/" +
188  "1.0#primaryImageURL")), null,
189  null).QueryInterface(Components.
190  interfaces.nsIFileURL).file.path;
191  } catch (e){}
192 
193  // Push the item's entry into the playlist's entry
194  plEntry.items.push(entry);
195  }
196  }
197  // Push the playlist's entry into the result
198  this.crt.push(plEntry);
199  }
200  } catch(e) {
201  foldersync.central.logEvent("sync-getItems",
202  "GetItems thread terminated " +
203  "unexpected:\n\n" + e, 1,
204  "chrome://foldersync/content/sync.js",
205  e.lineNumber);
206  this.crt = null;
207  }
208  },
209  },
210  // The sync thread is responsible for the sync itself (in separate thread)
211  sync: {
212  // The thread object we're running in
213  _thread: null,
214  // The main thread's thread object
215  _mainthread: null,
216  // True, if the thread is currently running
217  _running: false,
218 
219  // The OS we're running on
220  _OS: null,
221 
222  // The current amount of task points
223  _crtPoints: null,
224  // The amount of task points the last message was sent
225  _lstPoints: null,
226  // The maximum amount of task points (100%)
227  _maxPoints: null,
228 
229  // Initializes the sync thread (MAINTHREAD ONLY)
230  _init: function(){
231  try {
232  foldersync.central.logEvent("sync-sync",
233  "Initialisate sync thread", 5);
234  this._thread = Components.classes["@mozilla.org/thread-manager;1"].
235  getService(Components.interfaces.nsIThreadManager).
236  newThread(0);
237  this._mainthread =
238  Components.classes["@mozilla.org/thread-manager;1"].
239  getService(Components.interfaces.nsIThreadManager).
241  this._OS = foldersync.central.getOS();
242  } catch (e) {
243  foldersync.central.logEvent("sync-sync",
244  "Sync thread initialisation " +
245  "failed:\n\n" + e, 1,
246  "chrome://foldersync/content/sync.js",
247  e.lineNumber);
248  }
249  },
250 
251  // Start the sync thread. (MAINTHREAD ONLY)
252  _start: function(){
253  try {
254  foldersync.central.logEvent("sync-sync",
255  "Start sync thread", 5);
256  this._thread.dispatch(this, Components.interfaces.nsIThread.
257  DISPATCH_NORMAL);
258  } catch (e) {
259  foldersync.central.logEvent("sync-sync",
260  "Starting sync thread failed:\n\n" + e,
261  1,
262  "chrome://foldersync/content/sync.js",
263  e.lineNumber);
264  }
265  },
266 
267  // Initialize and start if necessary. (MAINTHREAD ONLY)
268  ensureRunning: function(){
269  if (!this._thread)
270  this._init();
271  if (!this._running)
272  this._start();
273  },
274 
275  // Are we running?
276  isSyncing: function(){
277  return this._running;
278  },
279 
280  /* Notify listeners of a change
281  * state (state): the state to send to listeners
282  * syncqueueobject (syncqueueobject): the object to send
283  */
284  _notify: function(state, syncqueueobject){
285  try {
286  foldersync.central.logEvent("sync-sync",
287  "Notify listeners of new state", 5);
288  // Write current data to update thread
289  foldersync.sync._threads.update.crt.state = state;
290  foldersync.sync._threads.update.crt.
291  syncqueueobject = syncqueueobject;
292  // Start update thread, we'll wait till it is completed
293  this._mainthread.dispatch(foldersync.sync._threads.update,
294  Components.interfaces.nsIThread.
295  DISPATCH_SYNC);
296  } catch (e) {
297  foldersync.central.logEvent("sync-sync",
298  "Starting update thread failed:\n\n" + e,
299  1,
300  "chrome://foldersync/content/sync.js",
301  e.lineNumber);
302  }
303  },
304 
305  // Get next SyncQueueObject or null if there is no next object
306  _getNext: function(){
307  try {
308  foldersync.central.logEvent("sync-sync",
309  "Get the next SyncQueueObject", 5);
310  // Start getQueue thread, we'll wait till it is completed
311  this._mainthread.dispatch(foldersync.sync._threads.getQueue,
312  Components.interfaces.nsIThread.
313  DISPATCH_SYNC);
314  return foldersync.sync._threads.getQueue.crt;
315  } catch (e) {
316  foldersync.central.logEvent("sync-sync",
317  "Starting getQueue thread failed:\n\n" +
318  e, 1,
319  "chrome://foldersync/content/sync.js",
320  e.lineNumber);
321  }
322  },
323 
324  /* Get a specific Profile or null if there is no profile with the GUID
325  * guid (string): the GUID of the Profile
326  */
327  _getProfile: function(guid){
328  try {
329  foldersync.central.logEvent("sync-sync",
330  "Get Profile " + guid, 5);
331  // Write GUID to getProfile thread
332  foldersync.sync._threads.getProfile.crt = guid;
333  // Start getProfile thread, we'll wait till it is completed
334  this._mainthread.dispatch(foldersync.sync._threads.getProfile,
335  Components.interfaces.nsIThread.
336  DISPATCH_SYNC);
337  return foldersync.sync._threads.getProfile.crt;
338  } catch (e) {
339  foldersync.central.logEvent("sync-sync",
340  "Starting getProfile thread failed:" +
341  "\n\n" + e, 1,
342  "chrome://foldersync/content/sync.js",
343  e.lineNumber);
344  }
345  },
346 
347  /* Get all media items in the playlists with the given GUIDs, see the
348  * getItems thread for details on the result.
349  * guids (array of string): the GUIDs of the playlists
350  */
351  _getItems: function(guids){
352  try {
353  foldersync.central.logEvent("sync-sync",
354  "Get Items from " + guids, 5);
355  // Write GUIDs to getItems thread
356  foldersync.sync._threads.getItems.crt = guids;
357  // Start getItems thread, we'll wait till it is completed
358  this._mainthread.dispatch(foldersync.sync._threads.getItems,
359  Components.interfaces.nsIThread.
360  DISPATCH_SYNC);
361  return foldersync.sync._threads.getItems.crt;
362  } catch (e) {
363  foldersync.central.logEvent("sync-sync",
364  "Starting getItems thread failed:\n\n" +
365  e, 1,
366  "chrome://foldersync/content/sync.js",
367  e.lineNumber);
368  }
369  },
370 
371  /* Get all nsIFiles of the (sub-)folder in 2 arrays
372  * folder (nsIFile): the folder we want to enumerate
373  * cArray (2 arrays of nsIFile): the files/folders we want to add
374  */
375  _enumFolder: function(folder, cArray){
376  foldersync.central.logEvent("sync-sync", "Enumerate '" + folder.path +
377  "'", 5);
378  // make sure we have an array
379  var result = {};
380  result.files = cArray ? (cArray.files ? cArray.files : []) : [];
381  result.folders = cArray ? (cArray.folders ? cArray.folders : []) : [];
382  // Get all entries
383  var entries = null;
384  try {
385  entries = folder.directoryEntries;
386  } catch (e) {
387  foldersync.central.logEvent("sync-sync",
388  "Failed to access '" + folder.path +
389  "':\n\n" + e, 2,
390  "chrome://foldersync/content/sync.js",
391  e.lineNumber);
392  return result;
393  }
394  // Go through all entries and register them
395  while(entries.hasMoreElements())
396  {
397  var ent = entries.getNext();
398  ent.QueryInterface(Components.interfaces.nsIFile);
399  if (ent.isFile()) {
400  result.files.push(ent);
401  }
402  if (ent.isDirectory()){
403  result = this._enumFolder(ent, result);
404  result.folders.push(ent);
405  }
406  }
407  // Return the enumerated files
408  return result;
409  },
410 
411  /* Ensures that there are only valid characters in a string, if pref set
412  * string (string): the string that should be corrected and returned
413  * profile (profile): the profile that should be used
414  */
415  _ensureCharacters: function(string, profile){
416  var aString = new String (string); // the string to check
417  var rBuffer = ""; // a buffer to write to
418  var result = ""; // the result
419  var waitChar = profile.advanced.cutSpaces; // Wait for char, no spaces
420  // Go through all characters, and write them to result
421  for (var i = 0; i < aString.length; i++){
422  var found = false;
423  for each (var bChar in profile.advanced.blockedChars)
424  if (aString[i] == bChar)
425  found = true;
426  if (found){
427  rBuffer += profile.advanced.replaceChar;
428  waitChar = false; // we had a character
429  }
430  else {
431  if (profile.advanced.cutSpaces)
432  if (aString[i] == " "){
433  // cut spaces on beginning
434  if (waitChar)
435  continue;
436  // cut spaces on end
437  rBuffer += " ";
438  continue;
439  }
440  rBuffer += aString[i];
441  // Append buffer on valid chars
442  result += rBuffer;
443  rBuffer = "";
444  waitChar = false; // we had a character
445  }
446  }
447  // We might need replace characters at the string's end
448  if (!profile.advanced.cutReplaced){
449  // check and drop spaces if pref is set
450  if (profile.advanced.cutSpaces){
451  var nBuffer = "";
452  var sBuffer = "";
453  for each (var c in rBuffer)
454  if (c == " ")
455  sBuffer += c;
456  else {
457  nBuffer += sBuffer;
458  sBuffer = "";
459  nBuffer += c;
460  }
461  result += nBuffer;
462  } else
463  result += rBuffer;
464  }
465  return result;
466  },
467 
468  /* Replace tags (%%) with their values
469  * crt (string): the user-entered string
470  * tags (tags): the track's tags
471  * playlist (string): the track's playlist name
472  * profile (profile): the profile to use
473  */
474  _replaceTags: function(crt, tags, playlist, profile){
475  // We'll store the result here, start with the given string
476  var result = crt;
477  // Get the tags
478  var artist = tags.artist ? tags.artist : profile.defaulttags.artist;
479  var title = tags.title ? tags.title : profile.defaulttags.title;
480  var album = tags.album ? tags.album : profile.defaulttags.album;
481  var albumartist = tags.albumartist ? tags.albumartist :
482  ( profile.fallbacks.albumartist ?
483  artist : profile.defaulttags.
484  albumartist );
485  var discnumber = tags.discnumber ? tags.discnumber :
486  profile.defaulttags.discnumber;
487  var composer = tags.composer ? tags.composer :
488  profile.defaulttags.composer;
489  var genre = tags.genre ? tags.genre : profile.defaulttags.genre;
490  var rating = tags.rating ? tags.rating : profile.defaulttags.rating;
491  var year = tags.year ? tags.year : profile.defaulttags.year;
492  var disccount = tags.disccount ? tags.disccount :
493  profile.defaulttags.disccount;
494  var producer = tags.producer ? tags.producer :
495  profile.defaulttags.producer;
496  var tracknum = tags.tracknum ? tags.tracknum :
497  profile.defaulttags.tracknumber;
498  while (tracknum.length < profile.structure.tnDigits){
499  tracknum = "0" + tracknum;
500  }
501  // Replace 'em
502  result = result.replace(/%artist%/g, artist);
503  result = result.replace(/%title%/g, title);
504  result = result.replace(/%album%/g, album);
505  result = result.replace(/%albumartist%/g, albumartist);
506  result = result.replace(/%discnumber%/g, discnumber);
507  result = result.replace(/%composer%/g, composer);
508  result = result.replace(/%genre%/g, genre);
509  result = result.replace(/%rating%/g, rating);
510  result = result.replace(/%playlist%/g, playlist);
511  result = result.replace(/%year%/g, year);
512  result = result.replace(/%disccount%/g, disccount);
513  result = result.replace(/%producer%/g, producer);
514  result = result.replace(/%tracknumber%/g, tracknum);
515  // Return the file-name safe form
516  return this._ensureCharacters(result, profile);
517  },
518 
519  /* Updates the point value, sends notification, returns true for cancel
520  * value (integer): number of task points completed
521  * syncqueueobject (syncqueueobject): the object for notification
522  */
523  _updateTaskPoints: function(value, syncqueueobject){
524  this._crtPoints += value;
525  if ((this._crtPoints/this._maxPoints)-0.01 >=
526  (this._lstPoints/this._maxPoints)){
527  this._lstPoints = this._crtPoints;
528  this._notify({
529  event: "status-update",
530  value: (this._crtPoints/this._maxPoints)*100
531  }, syncqueueobject);
532  }
533  return syncqueueobject.cancel;
534  },
535 
536  // The Thread's code
537  run: function(){
538  try {
539  this._running = true;
540  foldersync.central.logEvent("sync-sync",
541  "Sync thread started", 4);
542  // Process the whole queue
543  var crtObject = this._getNext();
544  while (crtObject){
545  // the current Sync
546  var crtSync = crtObject.sync;
547 
548  foldersync.central.logEvent("sync-sync",
549  "Sync started: \n\n" +
550  "Target: " + crtSync.
551  targetFolder + "\n" +
552  "Profile: " + crtSync.
553  profileGUID + "\n", 4);
554  // notify listeners that we started a new sync
555  this._notify({value:0, event:"started"}, crtObject);
556 
557  // get the current profile
558  var crtProfile = this._getProfile(crtSync.profileGUID);
559  // Make sure we got a profile
560  if (!crtProfile){
561  foldersync.central.logEvent("sync-sync",
562  "Sync error:\n\n" + "Profile " +
563  crtSync.profileGUID + " is not " +
564  "avaiable, Sync cancelled.", 1,
565  "chrome://foldersync/content/" +
566  "sync.js");
567  crtObject.cancel = true;
568  this._notify({value:0, event:"failed-noprofile"}, crtObject);
569  // get next Object
570  crtObject = this._getNext();
571  continue;
572  }
573  // Get the target folder
574  var tFolder = Components.classes["@mozilla.org/file/local;1"].
575  createInstance(Components.interfaces.nsILocalFile);
576  try {
577  tFolder.initWithPath(crtSync.targetFolder);
578  } catch(e){
579  foldersync.central.logEvent("sync-sync",
580  "Sync error:\n\n" + "Folder '" +
581  crtSync.targetFolder + "' is" +
582  "invalid. Sync cancelled.", 1,
583  "chrome://foldersync/content/" +
584  "sync.js", e.lineNumber);
585  this._notify({value:0, event:"failed-invalid"}, crtObject);
586  // get next Object
587  crtObject = this._getNext();
588  continue;
589  }
590  if (!tFolder.exists()){
591  foldersync.central.logEvent("sync-sync",
592  "Sync error:\n\n" + "Folder '" +
593  crtSync.targetFolder + "' does" +
594  "not exist. Sync cancelled.", 1,
595  "chrome://foldersync/content/" +
596  "sync.js");
597  this._notify({value:0, event:"failed-invalid"}, crtObject);
598  // get next Object
599  crtObject = this._getNext();
600  continue;
601  }
602 
603  // Get files at target
604  var tFiles = this._enumFolder(tFolder);
605  var tFolders = tFiles.folders;
606  tFiles = tFiles.files;
607 
608  // Get the items to sync
609  var crtItems = this._getItems(crtSync.playlists);
610  if (!crtItems){
611  foldersync.central.logEvent("sync-sync",
612  "Sync error:\n\n" + "Could not get" +
613  "media items for playlists, " +
614  "Sync cancelled.", 1,
615  "chrome://foldersync/content/" +
616  "sync.js");
617  this._notify({value:0, event:"failed-noitems"}, crtObject);
618  // get next Object
619  crtObject = this._getNext();
620  continue;
621  }
622 
623  //If required, sort items according to the criteria defined in the sorting scheme
624  if (crtProfile.playlists.isEnabled && crtProfile.playlists.isSorted){
625  //Prepare array with tag names in order of precedence according to sorting scheme, with related sort orders (ascending or descending)
626  var tagRankTmp2 = null;
627  var tagRankTmp = crtProfile.playlists.sortingScheme.match(/^%[a-z]+(:[ad])?%(,%[a-z]+(:[ad])?%)*$/g);
628  var tagRank = [];
629  if (!tagRankTmp){
630  foldersync.central.logEvent("sync-sync",
631  "Sync error:\n\n" + "Bad sorting" +
632  " schema, " +
633  "Sync cancelled.", 1,
634  "chrome://foldersync/content/" +
635  "sync.js");
636  this._notify({value:0, event:"failed-badsortingschema"}, crtObject);
637  // get next Object
638  crtObject = this._getNext();
639  continue;
640  }
641  tagRankTmp = crtProfile.playlists.sortingScheme.match(/%([a-z]+:?[ad]?)%/g);
642  for (var i=0;i<tagRankTmp.length;i++){
643  tagRankTmp2 = tagRankTmp[i].match(/^%([a-z]+):?([ad])?%$/);
644  if ((tagRankTmp2[2] == null) || (tagRankTmp2[2] == "a")){
645  tagRank.push({name : "%"+tagRankTmp2[1]+"%", order : 1});
646  }
647  else {
648  tagRank.push({name : "%"+tagRankTmp2[1]+"%", order : -1});
649  }
650  }
651  // Functions that sort items within a media list according to a scheme
652  var compStr = function(a, b){
653  if (!a && !b) return 0; // null equals null
654  if (!a && b) return -1; // null is lower than everything
655  if (!b && a) return 1; // null is lower than everything
656  var as = a;
657  var bs = b;
658  if (as == bs){return 0;}
659  var pos = 0;
660  var max = as.length > bs.length ? as.length : bs.length;
661  while ((as[pos] == bs[pos]) && (pos < max)) {pos++}
662  var result = as.charCodeAt(pos) - bs.charCodeAt(pos);
663  if (result == 0)
664  return as.length - bs.length;
665  return result;
666  };
667  var compareItems = function(a, b) {
668  var ret = 0;
669  for (var i=0;i<tagRank.length;i++) {
670  switch (tagRank[i].name){
671  case "%artist%":
672  ret = compStr(a.tags.artist, b.tags.artist)*tagRank[i].order;
673  break;
674  case "%title%":
675  ret = compStr(a.tags.title, b.tags.title)*tagRank[i].order;
676  break;
677  case "%album%":
678  ret = compStr(a.tags.album, b.tags.album)*tagRank[i].order;
679  break;
680  case "%genre%":
681  ret = compStr(a.tags.genre, b.tags.genre)*tagRank[i].order;
682  break;
683  case "%rating%":
684  ret = compStr(a.tags.rating, b.tags.rating)*tagRank[i].order;
685  break;
686  case "%tracknumber%":
687  ret = ((a.tags.tracknum?a.tags.tracknum:0)-(b.tags.tracknum?b.tags.tracknum:0))*tagRank[i].order;
688  break;
689  case "%playlist%":
690  ret = compStr(a.tags.playlist, b.tags.playlist)*tagRank[i].order;
691  break;
692  case "%albumartist%":
693  ret = compStr(a.tags.albumartist, b.tags.albumartist)*tagRank[i].order;
694  break;
695  case "%year%":
696  ret = compStr(a.tags.year, b.tags.year)*tagRank[i].order;
697  break;
698  case "%discnumber%":
699  ret = ((a.tags.discnumber?a.tags.discnumber:0)-(b.tags.discnumber?b.tags.discnumber:0))*tagRank[i].order;
700  break;
701  case "%disccount%":
702  ret = ((a.tags.disccount?a.tags.disccount:0)-(b.tags.disccount?b.tags.disccount:0))*tagRank[i].order;
703  break;
704  case "%composer%":
705  ret = compStr(a.tags.composer, b.tags.composer)*tagRank[i].order;
706  break;
707  case "%producer%":
708  ret = compStr(a.tags.producer, b.tags.producer)*tagRank[i].order;
709  break;
710  default:
711  //Unknown sorting tag name: ignored in tag sorting
712  ret = 0;
713  }
714  if (ret!=0) return ret;
715  }
716  return 0;
717  };
718  for each (var plEntry in crtItems){
719  try {
720  plEntry.items.sort(compareItems);
721  } catch (e) {
722  foldersync.central.logEvent("sync-sync",
723  "Sorting playlists failed:\n\n" +
724  e, 1,
725  "chrome://foldersync/content/sync.js",
726  e.lineNumber);
727  throw e;
728  }
729  }
730  }
731 
732  // Get the prefix for relative paths in playlists
733  var plRel = "";
734  var plBase = null; // the playlist's folder (nsIFile)
735  if (crtProfile.playlists.isEnabled){
736  var relParts = crtProfile.playlists.toFolder.split("/");
737  var aFile = tFolder.clone(); // a file to track where we are
738  for each (var part in relParts){
739  // Add inverse parts to plRel
740  switch (part){
741  case ".":
742  break;
743  case "..":
744  if (aFile.parent){
745  plRel = aFile.leafName +
746  crtProfile.playlists.splitChar + plRel;
747  aFile = aFile.parent;
748  } else {
749  foldersync.central.logEvent("sync-sync",
750  "Relative path for " +
751  "playlist not " +
752  "avaiable:\n\n" +
753  "No parent directory " +
754  "for '" + aFile.path + "'",
755  1, "chrome://foldersync/" +
756  "content/sync.js");
757  }
758  break;
759  default:
760  try {
761  aFile.append(part);
762  plRel = ".." + crtProfile.playlists.splitChar +
763  plRel;
764  } catch (e) {
765  foldersync.central.logEvent("sync-sync",
766  "Path for " +
767  "playlist not " +
768  "avaiable:\n\n" + e,
769  1, "chrome://foldersync/" +
770  "content/sync.js",
771  e.lineNumber);
772  }
773  }
774  }
775  // and there we are!
776  if (crtProfile.playlists.doRelativePoint)
777  plRel = "." + crtProfile.playlists.splitChar + plRel;
778  plBase = aFile;
779  foldersync.central.logEvent("sync-sync", "Playlist's path " +
780  "prefix is '" + plRel + "'", 5);
781  }
782 
783  // This will store error messages here
784  var failedFiles = [];
785  // These arrays will store what things are to copy/update or delete
786  var copyFiles = []; //{from:<nsIFile>, to:<nsIFile>, name:<string>}
787  var keepFiles = []; //nsIFile, files we should keep at target
788  var deleteFiles = []; //nsIFile
789  // This stores the number of songs we enumerated
790  var numEntries = 0;
791  // Go through all Playlists
792  for each (var pList in crtItems){
793  foldersync.central.logEvent("sync-sync", "Prepare playlist '" +
794  pList.name + "'", 5);
795  var playlist = null; // This holds the playlist, if any
796  // Playlist init code
797  if (crtProfile.playlists.isEnabled){
798  switch (crtProfile.playlists.format){
799  case "m3u":
800  playlist = "";
801  break;
802  case "m3uEx":
803  playlist = "#EXTM3U\r\n";
804  break;
805  }
806  }
807  // Go through all Songs
808  for each (var song in pList.items){
809  foldersync.central.logEvent("sync-sync", "Prepare '" +
810  song.path + "'", 5);
811  // Get the current file
812  var crtFile = Components.classes["@mozilla.org/file/local;1"].
813  createInstance(Components.interfaces.
814  nsILocalFile);
815  try {
816  crtFile.initWithPath(song.path);
817  } catch(e){
818  foldersync.central.logEvent("sync-sync",
819  "File '" + crtFile.path + "' " +
820  "is invalid:\n\n" + e, 2,
821  "chrome://foldersync/content/" +
822  "sync.js", e.lineNumber);
823  // Go to the next one...
824  continue;
825  }
826  if (!crtFile.exists()){
827  foldersync.central.logEvent("sync-sync",
828  "File '" + crtFile.path +
829  "' does not exist.", 2,
830  "chrome://foldersync/content/" +
831  "sync.js");
832  // Go to the next one...
833  continue;
834  }
835  // Get the target sub folder and the target file name
836  var tSubFolder = tFolder.clone();
837  var tSubFolders = []; // array of sub-folder names for playlist
838  var tName = crtFile.leafName;
839  if (crtProfile.structure.isEnabled){
840  // Get target folder and file name
841  var tParts = crtProfile.structure.schema.split("/");
842  for (var i = 0; i < tParts.length; i++){
843  var cItem = this._replaceTags(tParts[i], song.tags,
844  pList.name, crtProfile);
845  if (i != tParts.length - 1){
846  // Append if it is a folder
847  tSubFolder.append(cItem);
848  tSubFolders.push(cItem);
849  // If it is the cover folder we might copy the cover
850  if (crtProfile.structure.doCovers &&
851  (tParts[i] == crtProfile.structure.coverSchema) &&
852  song.tags.coverpath){
853  // Generate cover file name with extension
854  var coverName = crtProfile.structure.coverFile;
855  var sCover = song.tags.coverpath.split(".");
856  if (sCover.length > 1)
857  coverName += "." + sCover[sCover.length-1];
858  // Get the cover file
859  var coverF = tSubFolder.clone();
860  coverF.append(coverName);
861  // We won't copy if there is already a cover file
862  var exists = coverF.exists();
863  if (!exists)
864  for each (var file in copyFiles){
865  if ((file.to == tSubFolder.path) &&
866  (file.name == coverName)){
867  exists = true;
868  break;
869  }
870  }
871  if (!exists){
872  // Get cover file
873  var coverSF = Components.classes["@mozilla.org/" +
874  "file/local;1"].
875  createInstance(Components.interfaces.
876  nsILocalFile);
877  try {
878  coverSF.initWithPath(song.tags.coverpath);
879  if (!coverSF.exists())
880  throw Components.Exception(song.tags.coverpath +
881  " does not exist.");
882  // Push the file in the to copy array, if not there
883  var copyFile = {
884  from: coverSF,
885  to: tSubFolder.clone(),
886  name: coverName
887  };
888  var found = false;
889  for each (var cf in copyFiles)
890  if ((cf.from.path == copyFile.from.path) &&
891  (cf.to.path == copyFile.to.path) &&
892  (cf.name == copyFile.name))
893  found = true;
894  if (!found){
895  foldersync.central.logEvent("sync-sync",
896  "Cover '" +
897  song.tags.coverpath +
898  "' will go to '" +
899  coverF.path + "'",
900  5);
901  copyFiles.push({
902  from: coverSF,
903  to: tSubFolder.clone(),
904  name: coverName
905  });
906  }
907 
908  } catch (e){
909  foldersync.central.logEvent("sync-sync",
910  "Cover file '" + song.
911  tags.coverpath + "' " +
912  "caused unexpected " +
913  "Problem:\n\n" + e, 1,
914  "chrome://foldersync/" +
915  "content/sync.js",
916  e.lineNumber);
917  }
918  } else {
919  keepFiles.push(coverF); // we won't delete the file
920  foldersync.central.logEvent("sync-sync", "Cover " +
921  "exists already at '" +
922  coverF.path + "'", 5);
923  }
924  }
925  } else {
926  // Use as file name if it isn't a folder
927  tName = cItem;
928  // Append file extension, if any
929  var sName = crtFile.leafName.split(".");
930  if (sName.length > 1){
931  tName += "." + sName[sName.length-1];
932  }
933  // Maximum file name length
934  if (tName.length > crtProfile.advanced.fMaxLength) {
935  var temp_name = "";
936  for (var z = 0; z < crtProfile.advanced.fMaxLength-22;
937  z++){
938  temp_name += tName[z];
939  }
940  temp_name += crtProfile.advanced.replaceChar;
941  for (var z = tName.length-20; z < tName.length; z++){
942  temp_name+=tName[z];
943  }
944  tName=temp_name;
945  }
946  }
947  }
948  }
949  // Get the target file
950  var tFile = tSubFolder.clone();
951  tFile.append(tName);
952 
953  // Add the item to the playlist, if any
954  if (crtProfile.playlists.isEnabled){
955  // Get the relative path for the current song
956  var relPath = plRel;
957  for each (var part in tSubFolders){
958  relPath += part + crtProfile.playlists.splitChar;
959  }
960  relPath += tName;
961  // Add the line(s) to the playlist
962  switch (crtProfile.playlists.format){
963  case "m3uEx":
964  playlist += "\r\n#EXTINF:" +
965  Math.round(song.tags.duration / 1000000) +
966  "," + (song.tags.artist ? song.tags.artist +
967  " - " : "") +
968  (song.tags.title ? song.tags.title :
969  crtProfile.defaulttags.title) + "\r\n";
970  case "m3u":
971  playlist += relPath + "\r\n";
972  break;
973  }
974  }
975 
976  // Check if the file exists, and decide if we want to overwrite
977  if (tFile.exists()){
978  if (crtProfile.flags.doUpdate){
979  /* Compare last changed date, if preference is set,
980  * 2000 because of FAT storing in 2 second steps
981  */
982  if (((tFile.lastModifiedTime + 2000 >
983  crtFile.lastModifiedTime) &&
984  (tFile.lastModifiedTime - 2000 <
985  crtFile.lastModifiedTime))){
986  foldersync.central.logEvent("sync-sync", "File '" +
987  tFile.path + "' hasn't " +
988  "modifyed, won't copy it.",5);
989  keepFiles.push(tFile); // we won't delete the file
990  // Go to the next one...
991  continue;
992  }
993  } else {
994  foldersync.central.logEvent("sync-sync", "File '" +
995  tFile.path + "' exists " +
996  "already, won't copy it.",5);
997  keepFiles.push(tFile); // we won't delete the file
998  // Go to the next one...
999  continue;
1000  }
1001  }
1002 
1003  // Add to the copy array, if it is not there (another playlist)
1004  var copyFile = {from: crtFile, to: tSubFolder, name: tName};
1005  var found = false;
1006  for each (var cf in copyFiles)
1007  if ((cf.from.path == copyFile.from.path) &&
1008  (cf.to.path == copyFile.to.path) &&
1009  (cf.name == copyFile.name))
1010  found = true;
1011  if (!found){
1012  foldersync.central.logEvent("sync-sync", "File '" +
1013  song.path + "' will go to '" +
1014  tSubFolder.path + "' with " +
1015  "name '" + tName + "'", 5);
1016  copyFiles.push(copyFile);
1017  } else
1018  foldersync.central.logEvent("sync-sync", "File '" +
1019  song.path + "' already in " +
1020  "copy array to '" + tSubFolder.
1021  path + "' with name '" +
1022  tName + "'", 5);
1023 
1024  // We did a song, check if we want to notify
1025  numEntries++;
1026  if (numEntries % 1000 == 0)
1027  this._notify({value:0, event:"initing"}, crtObject);
1028  if (crtObject.cancel)
1029  break;
1030  }
1031  // Playlist finalize / write code
1032  if (crtProfile.playlists.isEnabled){
1033  var extension = "";
1034  switch (crtProfile.playlists.format){
1035  case "m3u":
1036  case "m3uEx":
1037  extension = "m3u";
1038  break;
1039  }
1040 
1041  // Get the playlist's nsIFile, and make sure it exists
1042  var pFile = plBase.clone();
1043  var pFileName = this._ensureCharacters(pList.name, crtProfile);
1044  pFile.append(pFileName + "." + extension);
1045  foldersync.central.logEvent("sync-sync",
1046  "Write playlist '" + pFile.path +
1047  "'", 5);
1048  try {
1049  if (!pFile.exists()){
1050  pFile.create(Components.interfaces.nsIFile.
1051  NORMAL_FILE_TYPE, 0664);
1052  }
1053 
1054  // Open (and empty) file for writing and init a converter
1055  var foStream = Components.classes["@mozilla.org/network/" +
1056  "file-output-stream;1"].
1057  createInstance(Components.interfaces.
1058  nsIFileOutputStream);
1059  foStream.init(pFile, 0x02 | 0x08 | 0x20, 0666, 0);
1060  var converter = Components.classes["@mozilla.org/intl/" +
1061  "converter-output-" +
1062  "stream;1"].
1063  createInstance(Components.interfaces.
1064  nsIConverterOutputStream);
1065  converter.init(foStream, crtProfile.playlists.encoding,
1066  0, 0);
1067  converter.writeString(playlist);
1068  converter.close();
1069 
1070  // Make sure we won't delete the playlist
1071  keepFiles.push(pFile);
1072  } catch (e){
1073  failedFiles.push("[+] " + pFile.path);
1074  foldersync.central.logEvent("sync-sync",
1075  "Writing playlist failed:\n\n" +
1076  e, 1,
1077  "chrome://foldersync/content/" +
1078  "sync.js",
1079  e.lineNumber);
1080  }
1081  }
1082  if (crtObject.cancel)
1083  break;
1084  }
1085 
1086  // Check for files that could get deleted (if pref set)
1087  // Build sorted lists of file names, to compare more efficient
1088  var deleteNames = [];//string
1089  if (crtProfile.flags.doUpdate && (!crtProfile.flags.doDelete))
1090  for each (var entry in copyFiles){
1091  var Entry = entry.to.clone();
1092  Entry.append(entry.name);
1093  var ePath = Entry.path;
1094  if (!crtProfile.advanced.compareCase)
1095  ePath = ePath.toUpperCase();
1096  deleteNames.push(ePath);
1097  }
1098  var keepNames = [];//string
1099  if (crtProfile.flags.doDelete)
1100  for each (var entry in keepFiles){
1101  var ePath = entry.path;
1102  if (!crtProfile.advanced.compareCase)
1103  ePath = ePath.toUpperCase();
1104  keepNames.push(ePath);
1105  }
1106  var compStr = function(a, b){
1107  if (!(a && b))
1108  return 0; // Prevent fail
1109  var as = a;
1110  var bs = b;
1111  if (as == bs){return 0;}
1112  var pos = 0;
1113  var max = as.length > bs.length ? as.length : bs.length;
1114  while ((as[pos] == bs[pos]) && (pos < max)) {pos++}
1115  var result = as.charCodeAt(pos) - bs.charCodeAt(pos);
1116  if (result == 0)
1117  return as.length - bs.length;
1118  return result;
1119  };
1120  keepNames.sort(compStr);
1121  deleteNames.sort(compStr);
1122  // Sort target file array
1123  var compFile = function(a, b){
1124  if (!(a && b))
1125  return 0; // Prevent fail
1126  var as = a.path;
1127  var bs = b.path;
1128  if (!crtProfile.advanced.compareCase){
1129  as = as.toUpperCase();
1130  bs = bs.toUpperCase();
1131  }
1132  return compStr(as, bs);
1133  };
1134  tFiles.sort(compFile);
1135  // Go through all files that are already at the target folder
1136  var dCur = 0; // Cursor for deleteNames
1137  var kCur = 0; // Cursor for keepNames
1138  for each (var file in tFiles){
1139  // Notify...
1140  numEntries++;
1141  if (numEntries % 1000 == 0)
1142  this._notify({value:0, event:"initing"}, crtObject);
1143  if (crtObject.cancel)
1144  break;
1145 
1146  var shouldDelete = false;
1147  var fPath = file.path;
1148  if (!crtProfile.advanced.compareCase)
1149  fPath = fPath.toUpperCase();
1150  // Look if the file should get keeped
1151  if (crtProfile.flags.doDelete){
1152  while ((compStr(keepNames[kCur],fPath) < 0) &&
1153  (kCur < keepNames.length -1))
1154  kCur++;
1155  if (keepNames[kCur] == fPath)
1156  shouldDelete = false;
1157  else
1158  shouldDelete = true;
1159  } else {
1160  // Look if the file should get deleted
1161  while ((compStr(deleteNames[dCur],fPath) < 0) &&
1162  (dCur < deleteNames.length -1))
1163  dCur++;
1164  if (deleteNames[dCur] == fPath)
1165  shouldDelete = true;
1166  }
1167  foldersync.central.logEvent("sync-sync",
1168  "File '" + file.path + "' " +
1169  (shouldDelete ? "will " : "won't ") +
1170  "get deleted.", 5);
1171  if (shouldDelete)
1172  deleteFiles.push(file);
1173  }
1174 
1175  // We have now all information we need; start the sync!
1176  foldersync.central.logEvent("sync-sync",
1177  "Got all information, start sync.", 4);
1178  this._notify({value:0, event:"started-sync"}, crtObject);
1179 
1180  /* Calculate maximum number of taskPoints for progress bar
1181  * The following tasks are rated:
1182  * Delete of a file: 10 Point
1183  * Copy a file: 50 Points
1184  * Check if a folder can get deleted and delete it: 1 Point
1185  *
1186  * After every task completion with at least 1 % done since the
1187  * last notification, we'll send a notification to listeners.
1188  */
1189  this._maxPoints = deleteFiles.length * 10 +
1190  copyFiles.length * 50 +
1191  (crtProfile.flags.doDelete ?
1192  tFolders.length * 1 : 0);
1193  this._crtPoints = 0;
1194  this._lstPoints = 0;
1195 
1196  // Delete files
1197  if (!crtObject.cancel)
1198  for each (var file in deleteFiles){
1199  try {
1200  foldersync.central.logEvent("sync-sync",
1201  "Delete '" + file.path + "'", 5);
1202  file.remove(false);
1203  } catch(e){
1204  failedFiles.push("[-] " + file.path);
1205  foldersync.central.logEvent("sync-sync",
1206  "Could not delete file '" +
1207  file.path + "':\n\n" +
1208  e, 1,
1209  "chrome://foldersync/content/" +
1210  "sync.js", e.lineNumber);
1211  }
1212  if (this._updateTaskPoints(10,crtObject))
1213  break;
1214  }
1215 
1216  // Try to delete folders if option set
1217  if (!crtObject.cancel)
1218  if (crtProfile.flags.doDelete){
1219  for each (var folder in tFolders){
1220  try {
1221  // Delete folder if empty
1222  if (!folder.directoryEntries.hasMoreElements())
1223  folder.remove(false);
1224  } catch (e){
1225  failedFiles.push("[-] " + folder.path);
1226  foldersync.central.logEvent("sync-sync",
1227  "Failed to delete " +
1228  "empty folder '" + folder.
1229  path + "':\n\n" + e, 1,
1230  "chrome://foldersync/" +
1231  "content/sync.js",
1232  e.lineNumber);
1233  }
1234  if (this._updateTaskPoints(1,crtObject))
1235  break;
1236  }
1237  }
1238 
1239  // Copy new Files
1240  if (!crtObject.cancel)
1241  for each (var entry in copyFiles){
1242  try {
1243  foldersync.central.logEvent("sync-sync",
1244  "Copy '" + entry.from.path +
1245  "' to '" + entry.to.path + "' " +
1246  "with the name '" + entry.name +
1247  "'", 5);
1248  entry.from.copyTo(entry.to, entry.name);
1249  // Fix lastModifiedTime problems on non-Windows OS.
1250  if (this._OS != "Win32"){
1251  var tFile = entry.to.clone();
1252  tFile.append(entry.name);
1253  tFile.lastModifiedTime = entry.from.lastModifiedTime;
1254  }
1255  } catch(e){
1256  failedFiles.push("[+] " + entry.from.path + " > " +
1257  entry.to.path + " (" + entry.name + ")");
1258  foldersync.central.logEvent("sync-sync",
1259  "Could not copy file '" +
1260  entry.from.path + "' to '" +
1261  entry.to.path + "':\n\n" +
1262  e, 1,
1263  "chrome://foldersync/content/" +
1264  "sync.js", e.lineNumber);
1265  }
1266  if (this._updateTaskPoints(50,crtObject))
1267  break;
1268  }
1269 
1270  // Do a Rockbox back-sync, if preference is set
1271  if ((!crtObject.cancel) && (crtProfile.flags.doRockbox))
1272  foldersync.rockbox.doSync(tFolder.path);
1273 
1274  // If there was a cancel, we'll send a cancelled message
1275  if (crtObject.cancel){
1276  foldersync.central.logEvent("sync-sync",
1277  "Sync cancelled \n\n" +
1278  "Target: " + crtSync.
1279  targetFolder + "\n" +
1280  "Profile: " + crtSync.
1281  profileGUID + "\n", 4);
1282  this._notify({
1283  value:(this._crtPoints/this._maxPoints)*100,
1284  event:"cancelled"
1285  }, crtObject);
1286 
1287  } else {
1288  if (failedFiles.length == 0) {
1289  foldersync.central.logEvent("sync-sync",
1290  "Sync completed: \n\n" +
1291  "Target: " + crtSync.targetFolder +
1292  "\nProfile: " +
1293  crtSync.profileGUID + "\n", 4);
1294  // notify listeners that we finished the sync
1295  this._notify({value:100, event:"completed"}, crtObject);
1296  } else {
1297  foldersync.central.logEvent("sync-sync",
1298  "Sync completed with errors: " +
1299  "\n\nTarget: " + crtSync.
1300  targetFolder + "\nProfile: " +
1301  crtSync.profileGUID + "\n", 4);
1302  // notify listeners that we finished the sync
1303  this._notify({
1304  value:100,
1305  event:"completed-errors",
1306  errors:failedFiles,
1307  }, crtObject);
1308  }
1309  }
1310  // get next Object
1311  crtObject = this._getNext();
1312  }
1313  this._running = false;
1314  foldersync.central.logEvent("sync-sync",
1315  "Sync thread stopped", 4);
1316  } catch (e) {
1317  this._running = false;
1318  this._notify({event:"fatal"}, {sync:{}});
1319  foldersync.central.logEvent("sync-sync",
1320  "Sync thread terminated " +
1321  "unexpected:\n\n" + e, 1,
1322  "chrome://foldersync/content/sync.js",
1323  e.lineNumber);
1324  }
1325  },
1326  },
1327  },
1328 
1329  /* Notify listeners
1330  * state (state): the state to send
1331  * syncqueueobject (syncqueueobject): the queue object to send
1332  */
1333  _notify: function(state, syncqueueobject){
1334  try {
1335  foldersync.central.logEvent("sync",
1336  "Update listeners:\n\n" +
1337  "State: " + state.event + "\n" +
1338  " " + state.value + "\n" +
1339  "Cancelled: " + syncqueueobject.
1340  cancel + "\n" +
1341  "GUID:" + syncqueueobject.guid + "\n" +
1342  "Target: " + syncqueueobject.
1343  sync.targetFolder + "\n" +
1344  "Profile: " + syncqueueobject.
1345  sync.profileGUID, 5);
1346  for each (var listener in foldersync.sync._listeners){
1347  try {
1348  listener(state, syncqueueobject);
1349  } catch (e){
1350  foldersync.central.logEvent("sync",
1351  "Wasn't able to notify listener '" +
1352  listener + "':\n\n" + e, 3,
1353  "chrome://foldersync/content/" +
1354  "sync.js", e.lineNumber);
1355  }
1356  }
1357  } catch(e) {
1358  foldersync.central.logEvent("sync",
1359  "Listener update terminated " +
1360  "unexpected:\n\n" + e, 1,
1361  "chrome://foldersync/content/sync.js",
1362  e.lineNumber);
1363  }
1364  },
1365 
1366  // Startup code for the sync controller
1367  onLoad: function(e){
1368  foldersync.central.logEvent("sync", "Sync controller " +
1369  "initialisation started.", 5);
1370  // Init / Clear arrays for queue and listeners
1371  this._queue = [];
1372  this._listeners = [];
1373  // We're done!
1374  foldersync.central.logEvent("sync", "Sync controller started.", 4);
1375  },
1376  // Shutdown code for the sync controller
1377  onUnload: function(e){
1378  foldersync.central.logEvent("sync", "Sync controller " +
1379  "shutdown started.", 5);
1380  foldersync.central.logEvent("sync", "Sync controller stopped.", 4);
1381  },
1382 
1383  /* Generate a new Sync
1384  * folder (string): the target folder
1385  * profile (string): the GUID of the profile to use
1386  * plists (array of string): the GUIDs of the selected playlists
1387  */
1388  generateSync: function(folder, profile, plists){
1389  // return a sync with the given attributes
1390  return {
1391  targetFolder: folder, // The target folder
1392  profileGUID: profile, // The profile's GUID
1393  playlists: plists, // The playlists' GUIDs to use
1394  };
1395  },
1396 
1397  /* Generate a new SyncQueueObject
1398  * sync (sync): the sync the SyncQueueObject should be based on
1399  */
1400  generateSyncQueueObject: function(sync){
1401  // return a SyncQueueObject with the given sync
1402  return {
1403  sync: sync, // The sync
1404  guid: Components.classes["@mozilla.org/uuid-generator;1"].
1405  getService(Components.interfaces.nsIUUIDGenerator).generateUUID().
1406  toString(), // The GUID for referencing purposes
1407  cancel: false, // might be set to true, and the sync will be cancelled
1408  };
1409  },
1410 
1411  /* Add a SyncQueueObject to the sync queue
1412  * object (syncqueueobject)
1413  */
1414  addToQueue: function(object){
1415  try{
1416  foldersync.central.logEvent("sync",
1417  "Append SyncQueueObject:\n\n" +
1418  "GUID:" + object.guid +
1419  "\nTarget: " + object.sync.targetFolder +
1420  "\nProfile: " + object.sync.profileGUID, 4);
1421  this._queue.push(object);
1422  this._notify({value: 0, event:"added"}, object);
1423  } catch (e){
1424  foldersync.central.logEvent("sync",
1425  "Adding sync object to queue failed:\n\n" +
1426  e, 1,
1427  "chrome://foldersync/content/sync.js",
1428  e.lineNumber);
1429  }
1430  },
1431 
1432  /* Remove a specific SyncQueueObject from the sync queue
1433  * object (syncqueueobject): the object to remove
1434  */
1435  removeFromQueue: function(object){
1436  try {
1437  foldersync.central.logEvent("sync",
1438  "Remove SyncQueueObject:\n\n" +
1439  "GUID:" + object.guid +
1440  "\nTarget: " + object.sync.targetFolder +
1441  "\nProfile: " + object.sync.profileGUID, 4);
1442  var foundindex = -1;
1443  for (var i = 0; i < this._queue.length; i++){
1444  if (this._queue[i].guid == object.guid){
1445  foundindex = i;
1446  break;
1447  }
1448  }
1449  if (foundindex != -1){
1450  this._queue.splice(foundindex,1);
1451  this._notify({value: 0, event:"removed"}, object);
1452  } else {
1453  foldersync.central.logEvent("sync",
1454  "Removing sync object from queue failed:" +
1455  "\n\nObject is not registered." + e, 2,
1456  "chrome://foldersync/content/sync.js");
1457  }
1458  } catch (e){
1459  foldersync.central.logEvent("sync",
1460  "Removing sync object from queue failed:" +
1461  "\n\n" + e, 1,
1462  "chrome://foldersync/content/sync.js",
1463  e.lineNumber);
1464  }
1465  },
1466 
1467  // Ensure that the the sync queue is being executed
1468  ensureQueueRunning: function(){
1469  this._threads.sync.ensureRunning();
1470  },
1471 
1472  // Is there a Sync running?
1473  isSyncing: function(){
1474  return this._threads.sync.isSyncing();
1475  },
1476 
1477  /* Add the given sync to queue, perform it and return the queue object's GUID
1478  * sync (sync): the sync to add
1479  */
1480  performSync: function(sync){
1481  try {
1482  var so = this.generateSyncQueueObject(sync);
1483  var result = so.guid;
1484  this.addToQueue(so);
1485  this.ensureQueueRunning();
1486  return result;
1487  } catch (e){
1488  foldersync.central.logEvent("sync",
1489  "Preparing queue for sync failed:\n\n" +
1490  e, 1,
1491  "chrome://foldersync/content/sync.js",
1492  e.lineNumber);
1493  }
1494  },
1495 
1496  /* Get avaiable playlist formats, return value: Array of Playlist formats
1497  * Each playlist format has the structure:
1498  * {
1499  * name: <string>, // the name to pass foldersync.central.getLocaleString
1500  * internalName: <string>, // the name to store with a profile
1501  * }
1502  */
1503  getPFormats: function(){
1504  return [
1505  { name: "playlistformat.m3u", internalName: "m3u" },
1506  { name: "playlistformat.m3uEx", internalName: "m3uEx" },
1507  ];
1508  },
1509 
1510  /* Add the given Listener to the list of listeners
1511  * listener (function(state,syncqueueobject)): the listener to add
1512  * a state has the following structure:
1513  * {
1514  * value:<integer>, // 0-100 progress
1515  * event:<string>, // the reason for a event to be fired, see sync thread
1516  * }
1517  */
1518  addListener: function(listener){
1519  try{
1520  foldersync.central.logEvent("sync",
1521  "Append listener:\n\n" +
1522  listener, 5);
1523  this._listeners.push(listener);
1524  } catch (e){
1525  foldersync.central.logEvent("sync",
1526  "Adding listener to list failed:\n\n" +
1527  e, 1,
1528  "chrome://foldersync/content/sync.js",
1529  e.lineNumber);
1530  }
1531  },
1532 
1533  /* Remove the given Listener
1534  * listener (function(state,syncqueueobject)): the listener to remove
1535  */
1536  removeListener: function(listener){
1537  try{
1538  foldersync.central.logEvent("sync",
1539  "Remove listener:\n\n" +
1540  listener, 5);
1541  var removed = false;
1542  // Search for the listener and remove it
1543  for (var i = 0; i < this._listeners.length; i++){
1544  if (this._listeners[i] == listener){
1545  foldersync.central.logEvent("sync",
1546  "Removed listener:\n\n" +
1547  this._listeners[i] + "\n" +
1548  "Index: " + i, 5);
1549  this._listeners.splice(i,1);
1550  removed = true;
1551  }
1552  }
1553  // Warn if the listener wasn't registered
1554  if (!removed)
1555  foldersync.central.logEvent("sync",
1556  "Removing listener from list failed:\n\n" +
1557  "'" + listener + "'" +
1558  "is no registered listener.", 2,
1559  "chrome://foldersync/content/sync.js");
1560  } catch (e){
1561  foldersync.central.logEvent("sync",
1562  "Removing listener from list failed:\n\n" +
1563  e, 1,
1564  "chrome://foldersync/content/sync.js",
1565  e.lineNumber);
1566  }
1567  },
1568 
1569  /* Generate a Error Message out of a state object
1570  * state (state): the sync state object
1571  * returns null if there is no error within this state, or structure:
1572  * {
1573  * error:<string>, // the error's short description
1574  * message:<string>, // the detailed error message, if any
1575  * fatal:<bool>, // if the error was fatal
1576  * }
1577  */
1578  generateErrorMessage: function(state){
1579  var result = null;
1580  if (state.event.split("failed-").length > 1){
1581  result = {};
1582  result.error = foldersync.central.getLocaleString("status.failed");
1583  switch (state.event){
1584  case "failed-noprofile":
1585  result.message = foldersync.central.getLocaleString("error.profile");
1586  break;
1587  case "failed-invalid":
1588  result.message = foldersync.central.getLocaleString("error.invalid");
1589  break;
1590  case "failed-noitems":
1591  result.message = foldersync.central.getLocaleString("error.noitems");
1592  break;
1593  case "failed-badsortingschema":
1594  result.message = foldersync.central.getLocaleString("error.badsortingschema");
1595  break;
1596  default:
1597  result.message = null;
1598  }
1599  result.fatal = true;
1600  }
1601  if (state.event == "completed-errors"){
1602  result = {};
1603  result.error = foldersync.central.getLocaleString("status.errors");
1604  result.message = foldersync.central.getLocaleString("error.some") + "\n";
1605  result.message += state.errors.join("\n");
1606  result.fatal = false;
1607  }
1608  if (state.event == "fatal"){ // This should NOT happen...
1609  result = {};
1610  result.error = foldersync.central.getLocaleString("status.failed");
1611  result.message = "";
1612  result.fatal = true;
1613  }
1614  return result;
1615  },
1616 
1617  // Get the current sync queue.
1618  getQueue: function(){
1619  // We send only a copy. See also note at getProfile thread
1620  return JSON.parse(JSON.stringify(this._queue));
1621  },
1622 };
GeneratorThread currentThread
classDescription entry
Definition: FeedWriter.js:1427
_dialogDatepicker pos
dataSBHighestRatedArtists SBProperties rating
Definition: tuner2.js:867
var event
var converter
foldersync sync
Definition: sync.js:14
sidebarFactory createInstance
Definition: nsSidebar.js:351
sbOSDControlService prototype QueryInterface
getService(Ci.sbIFaceplateManager)
let window
var getService(Components.interfaces.nsIWindowMediator).getMostRecentWindow('Songbird SBProperties artist
Definition: tuner2.js:40
_hideDatepicker duration
Lastfm onLoad
Definition: mini.js:36
sbDeviceFirmwareAutoCheckForUpdate prototype _queue
return null
Definition: FeedWriter.js:1143
return ret
countRef value
Definition: FeedWriter.js:1423
var JSON
var ios
Definition: head_feeds.js:5
sbDeviceFirmwareAutoCheckForUpdate prototype interfaces
function onUnload()
onUnload - called when the cover preview window unloads.
Definition: coverPreview.js:36
window addListener("unload", function(){window.removeListener("unload", arguments.callee);document.purge();if(Browser.Engine.trident){CollectGarbage();}})
_getSelectedPageStyle s i
var file