rockbox.js
Go to the documentation of this file.
1 /* rockbox.js
2  * This will be loaded into the main window, sync code might call foldersync.
3  * rockbox.doSync from main thread to perform a rockbox-backsync.
4  */
5 
6 if (typeof foldersync == 'undefined') {
7  var foldersync = {};
8 };
9 
10 /* Rockbox database file format brief summary.
11  *
12  * First 4 bytes are version id must be 0D 48 43 54 for this code to work.
13  *
14  * database_idx.tcd is the master index file
15  * header is 24 bytes including 4 byte version id
16  *
17  * then each track entry is 84 bytes consisting of 21 x 4 byte int
18  * int values are little endian so conversion is done here by fgeti
19  *
20  * entry 1 is byte offset into database_1.tcd for track album
21  * entry 3 is byte offset into database_3.tcd for track title
22  * entry 14 is track playcount
23  * entry 15 is track rating
24  *
25  * the other database files contain records that are referenced
26  * from the byte offsets in the master index file. each record
27  * is two 4 byte integers and a null terminated string.
28  *
29  * we use album+title to find the track in Songbird and
30  * then update the play count and rating properties only if
31  * a single record is found.
32  *
33  * the rockbox database play counts are local values that never get set
34  * from the id3 tags. hence, we sum the count into songbird and clear
35  * it in rockbox.
36  *
37  */
38 var HDR_SIZE = 6;
39 var TAG_COUNT = 21;
40 var DB_VERSION = 0x5443480d;
41 var DB_PREFIX = 8;
42 var TAG = { "Album":1, "Title":3, "PlayCount":14, "Rating":15, "Flags":20 };
43 var FLAG = { "Deleted":1 };
44 
45 foldersync.rockbox = {
46  /* Do a Rockbox back-sync
47  * path (string): the sync target path
48  */
49  doSync:function(path){
50  foldersync.central.logEvent("Rockbox", "Sync started.", 4);
51 
52  // get the main songbird library list
53  var Cc = Components.classes;
54  var Ci = Components.interfaces;
55  var songlib = LibraryUtils.mainLibrary;
56  foldersync.central.logEvent("Rockbox", "mainLibrary: "+songlib, 5);
57  var rockpath = this._findpath(path);
58 
59  // continue only if required rockbox database files exist
60  var idxhdr = [];
61  var idxdb = this._fopen(rockpath+"/database_idx.tcd");
62  if(idxdb == null) {
63  foldersync.central.logEvent("Rockbox", "idx database not found.", 1,
64  "chrome://foldersync/content/rockbox.js");
65  return;
66  }
67  var adb = this._fopen(rockpath+"/database_1.tcd");
68  if(adb == null) {
69  foldersync.central.logEvent("Rockbox", "album database (1) not found.",
70  1,
71  "chrome://foldersync/content/rockbox.js");
72  idxdb.fclose();
73  return;
74  }
75  var tdb = this._fopen(rockpath+"/database_3.tcd");
76  if(tdb == null) {
77  foldersync.central.logEvent("Rockbox", "title database (3) not found.",
78  1,
79  "chrome://foldersync/content/rockbox.js");
80  adb.fclose(); idxdb.fclose();
81  return;
82  }
83  // read the idx header
84  for(var i = 0; i < HDR_SIZE; i++)
85  idxhdr[i] = this._swapi(idxdb.fgeti());
86 
87  // make sure database is correct version
88  if(idxhdr[0] != DB_VERSION)
89  foldersync.central.logEvent("Rockbox", "Incompatible file version.",
90  1,
91  "chrome://foldersync/content/rockbox.js");
92  else {
93  // scan the rockbox database
94  while( idxdb.available() >= TAG_COUNT*4 ) {
95  var idxtags = [];
96  for(var i = 0; i < TAG_COUNT; i++)
97  idxtags[i] = this._swapi(idxdb.fgeti());
98  if( (idxtags[TAG.Flags] & FLAG.Deleted) != 0 )
99  continue;
100  adb.fseek(DB_PREFIX + idxtags[TAG.Album]);
101  var album = adb.fgets();
102  tdb.fseek(DB_PREFIX + idxtags[TAG.Title]);
103  var title = tdb.fgets();
104 
105  /* update songbird only if track has been played and isn't marked
106  * deleted
107  */
108  if( idxtags[TAG.PlayCount] > 0 ) {
109  var cnv = Cc['@mozilla.org/intl/scriptableunicodeconverter'].
110  getService(Ci.nsIScriptableUnicodeConverter);
111  cnv.charset = "UTF-8";
112  album = cnv.ConvertToUnicode(album); cnv.Finish();
113  title = cnv.ConvertToUnicode(title); cnv.Finish();
114  foldersync.central.logEvent("Rockbox", album + ":" + title +
115  " [" + idxtags[TAG.PlayCount] +
116  "]", 5);
117 
118  var key = Cc["@songbirdnest.com/Songbird/Properties/" +
119  "MutablePropertyArray;1"].
120  createInstance(Ci.sbIPropertyArray);
121  key.appendProperty(SBProperties.albumName, album);
122  key.appendProperty(SBProperties.trackName, title);
123  try {
124  var item = songlib.getItemsByProperties(key);
125  } catch(e) {
126  foldersync.central.logEvent("Rockbox", "Missing: " +
127  album + ":" + title + " [" +
128  idxtags[TAG.PlayCount] + "]",
129  3, "chrome://foldersync/" +
130  "content/rockbox.js");
131  continue;
132  }
133  // only update if we found a single matching songbird item
134  if(item.length == 1) {
135  // sum the count into songbird count
136  var media = item.enumerate().getNext();
137  media.setProperty(SBProperties.playCount,
138  Number(media.getProperty(SBProperties.
139  playCount)) +
140  idxtags[TAG.PlayCount]);
141  var pos = idxdb.ftell();
142  idxdb.fseek( pos-( TAG_COUNT - TAG.PlayCount )*4 );
143  idxdb.fputi(0); // clear the rockbox count value
144  idxdb.fseek(pos);
145  if(idxtags[TAG.Rating] != 0)
146  // rockbox 0-10 -> songbird 0-5
147  media.setProperty(SBProperties.rating,
148  idxtags[TAG.Rating]/2);
149  }
150  }
151  }
152  }
153  tdb.fclose();
154  adb.fclose();
155  idxdb.fclose();
156  foldersync.central.logEvent("Rockbox", "Sync finished.", 4);
157  },
158 
159  _findpath:function(path) {
160  var path = path + "/";
161  var chk = Components.classes["@mozilla.org/file/local;1"].
162  createInstance(Components.interfaces.nsILocalFile);
163  while(path.length > 0) {
164  chk.initWithPath(path+".rockbox");
165  foldersync.central.logEvent("Rockbox", "Chk:"+chk.path, 5);
166  if( chk.exists() )
167  return chk.path;
168  path = (path.split(/(\/)/).slice(0, -3).join(""));
169  }
170  return "";
171  },
172 
173  _swapi:function(i) {
174  return ( i>>>24 | (i>>>8 & 0xFF00) | (i<<8 & 0xFF0000) | i<<24 );
175  },
176 
177  _fopen:function(filename) {
178  try {
179  var f = Components.classes["@mozilla.org/file/local;1"].
180  createInstance(Components.interfaces.nsILocalFile);
181  f.initWithPath(filename);
182  if(!f.exists())
183  return null;
184 
185  var is = Components.classes["@mozilla.org/network/" +
186  "file-input-stream;1"].
187  createInstance(Components.interfaces.
188  nsIFileInputStream);
189  is.init(f, 0x04, -1, null);
190  is.QueryInterface(Components.interfaces.nsISeekableStream);
191 
192  var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
193  createInstance(Components.interfaces.
194  nsIBinaryInputStream);
195  bis.setInputStream(is);
196 
197  var os = Components.classes["@mozilla.org/network/" +
198  "file-output-stream;1"].
199  createInstance(Components.interfaces.
200  nsIFileOutputStream);
201  os.init(f, 0x04, -1, null);
202  os.QueryInterface(Components.interfaces.nsISeekableStream);
203 
204  var bos = Components.classes["@mozilla.org/binaryoutputstream;1"].
205  createInstance(Components.interfaces.
206  nsIBinaryOutputStream);
207  bos.setOutputStream(os);
208  } catch (e) {
209  foldersync.central.logEvent("Rockbox", "File exception:\n\n" + e, 1,
210  "chrome://foldersync/content/rockbox.js",
211  e.lineNumber);
212  }
213 
214  return {
215  "is": is, "os": os,
216  "bis": bis, "bos": bos,
217  "available": function() { return this.is.available(); },
218  "fclose": function() { this.bis.close(); this.is.close(); this.bos.close(); this.os.close(); },
219  "ftell": function() { return this.is.tell(); },
220  "fseek": function(pos) { this.is.seek(0,pos); this.os.seek(0,pos); },
221  "fgetc": function() { return this.bis.read8(); },
222  "fgeti": function() { return this.bis.read32(); },
223  "fgets": function() { rs=""; while((rc=this.bis.read8())!=0){rs=rs+String.fromCharCode(rc);} return rs; },
224  "fputi": function(i) { this.bos.write32(i); },
225  };
226  }
227 }
228 
var DB_PREFIX
Definition: rockbox.js:41
const Cc
_dialogDatepicker pos
var TAG
Definition: rockbox.js:42
var TAG_COUNT
Definition: rockbox.js:39
sidebarFactory createInstance
Definition: nsSidebar.js:351
getService(Ci.sbIFaceplateManager)
return null
Definition: FeedWriter.js:1143
var HDR_SIZE
Definition: rockbox.js:38
var os
var getService(Components.interfaces.nsIWindowMediator).getMostRecentWindow('Songbird SBProperties playCount
Definition: tuner2.js:40
const Ci
Javascript wrappers for common library tasks.
var DB_VERSION
Definition: rockbox.js:40
var FLAG
Definition: rockbox.js:43
_getSelectedPageStyle s i