sbMigrate19to110pre0.addMetadataHashIdentity.js
Go to the documentation of this file.
1 /*
2  *=BEGIN SONGBIRD GPL
3  *
4  * This file is part of the Songbird web player.
5  *
6  * Copyright(c) 2005-2011 POTI, Inc.
7  * http://www.songbirdnest.com
8  *
9  * This file may be licensed under the terms of of the
10  * GNU General Public License Version 2 (the ``GPL'').
11  *
12  * Software distributed under the License is distributed
13  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
14  * express or implied. See the GPL for the specific language
15  * governing rights and limitations.
16  *
17  * You should have received a copy of the GPL along with this
18  * program. If not, go to http://www.gnu.org/licenses/gpl.html
19  * or write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  *=END SONGBIRD GPL
23  */
24 
25 const Cc = Components.classes;
26 const Ci = Components.interfaces;
27 const Cr = Components.results;
28 const Cu = Components.utils;
29 
30 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
31 Cu.import("resource://app/jsmodules/sbLocalDatabaseMigrationUtils.jsm");
32 Cu.import("resource://app/jsmodules/SBJobUtils.jsm");
33 Cu.import("resource://app/jsmodules/sbProperties.jsm");
34 Cu.import("resource://app/jsmodules/GeneratorThread.jsm");
35 Cu.import("resource://app/jsmodules/sbLibraryUtils.jsm");
36 
37 const FROM_VERSION = 28;
38 const TO_VERSION = 29;
39 
40 function LOG(s) {
41  dump("----++++----++++sbLibraryMigration " +
42  FROM_VERSION + " to " + TO_VERSION + ": " +
43  s +
44  "\n----++++----++++\n");
45 }
46 
48 {
49  SBLocalDatabaseMigrationUtils.BaseMigrationHandler.call(this);
50  this._errors = [];
51 
52  /* We handle each contentType individually, and we keep extra information
53  * about each in these helper objects */
54  this._contentMigrations = {
55  "audio": {
56  /* The properties used when hashing metadata of an audio mediaitem.
57  * The order here is important and should match the order used by
58  * sbIdentityService */
59  props: [
60  SBProperties.trackName,
61  SBProperties.artistName,
62  SBProperties.albumName,
63  SBProperties.genre
64  ],
65 
66  // flag to indicate that audio files have been migrated
67  completed: false,
68  },
69 
70  "video": {
71  /* The properties used when hashing metadata of a video mediaitem.
72  * The order here is important and should match the order used by
73  * sbIdentityService */
74  props: [
75  SBProperties.trackName,
76  SBProperties.artistName,
77  SBProperties.albumName,
78  SBProperties.genre
79  ],
80 
81  // flag to indicate that video files have been migrated
82  completed: false,
83  },
84  }
85 
86  /* The separator character to put between each property when concatenated
87  * for hashing. This should match that used by sbIdentityService */
88  this.separator = '|';
89 
90  /* Initial values for sbIJobProgress attributes, will be updated when the
91  * number of hashes that will need to be calculated is known */
92  this._progress = 0;
93  this._total = 0;
94 }
95 
96 //-----------------------------------------------------------------------------
97 // sbLocalDatabaseMigration Implementation
98 //-----------------------------------------------------------------------------
99 
100 sbLibraryMigration.prototype = {
101  __proto__: SBLocalDatabaseMigrationUtils.BaseMigrationHandler.prototype,
102  classDescription: 'Songbird Migration Handler, version ' +
103  FROM_VERSION + ' to ' + TO_VERSION,
104  classID: Components.ID("{4eba22e9-d657-4599-a181-b8340852e7a2}"),
105  contractID: SBLocalDatabaseMigrationUtils.baseHandlerContractID +
106  FROM_VERSION + 'to' + TO_VERSION,
107 
108  fromVersion: FROM_VERSION,
109  toVersion: TO_VERSION,
110 
111  migrate: function sbLibraryMigration_migrate(aLibrary) {
112 
113  this._library = aLibrary;
114 
115  var sip = Cc["@mozilla.org/supports-interface-pointer;1"]
116  .createInstance(Ci.nsISupportsInterfacePointer);
117  sip.data = this;
118 
119  /* We use a generator thread so that we can update the progress dialog
120  * periodically throughout the migration. That only takes relatively
121  * infrequent updates and low CPU so give the migration thread more CPU
122  * and a longer period */
123  this._thread = new GeneratorThread(this.processItems());
124  this._thread.maxPctCPU = 95;
125  this._thread.period = 50;
126  this._thread.start();
127 
128  // Show the progress dialog tethered to this job
129  SBJobUtils.showProgressDialog(sip.data, null, 0);
130  },
131 
132  processItems: function sbLibraryMigration_processItems() {
133  try {
134  this._databaseGUID = this._library.databaseGuid;
135  this._databaseLocation = this._library.databaseLocation;
136 
137  // Set some basics for the dialog and check if we should update the dialog
138  this._titleText = "Library Migration Helper";
139  this._statusText = "Preparing to migrate 1.9 database to 1.10 database...";
140  yield this.checkIfShouldUpdateAndYield();
141 
142  // first query is for adding our new metadata hash col and index on it
143  var query = this.createMigrationQuery(this._library);
144  query.addQuery("alter table media_items add column metadata_hash_identity");
145  query.addQuery("alter table library_media_item add column metadata_hash_identity");
146  query.addQuery("create index idx_media_items_metadata_hash_identity" +
147  "on media_items (metadata_hash_identity)");
148 
149  query.addQuery("REINDEX");
150  query.addQuery("ANALYZE");
151  query.addQuery("COMMIT");
152 
153  query.setAsyncQuery(true);
154  query.execute();
155 
156  /* With hash column added, now we generate a hash for each existing item.
157  * The properties used for each contentType could be different so we
158  * handle them separately. */
159  for (var contentType in this._contentMigrations) {
160  yield this.hashExistingMediaItems(contentType);
161  }
162  }
163  catch (e) {
164  dump("Exception occured: " + e);
165  throw e;
166  }
167  },
168 
169  hashExistingMediaItems: function sbLibraryMigration_hashExistingMediaItems
170  (contentType) {
171  /* Make maps of property_name to property_id for the properties used when
172  * hashing audio and video files. The ids are used to get the property
173  * values for each mediaitem. */
174  var propNames = this._contentMigrations[contentType].props;
175  var propMap = this.getPropertyIDs(propNames);
176 
177  var idService = Cc["@songbirdnest.com/Songbird/IdentityService;1"]
178  .getService(Ci.sbIIdentityService);
179 
180  /* Build a query that will associate each media_item_id to all it's relevant
181  * properties in a single row so that we can easily access, concatenate, and
182  * then hash those properties.
183  * The order of the properties will be the same as in the passed propNames
184  * and should match the order used in sbIdentityService. As content_type is
185  * not stored in the resource_properties table but needs to be hashed,
186  * it is explicitly added in the initialization of selectSQL */
187  let selectSQL = "SELECT media_items.guid, media_items.content_mime_type";
188  let fromSQL = "FROM media_items"
189  let joinSQL = "";
190  let whereSQL = "WHERE content_mime_type = '" + contentType + "'" +
191  " AND media_items.is_list = '0'";
192 
193  for (var i = 0; i < propNames.length; i++) {
194  // Check if it's time for us to yield, and update the dialog if so
195  yield this.checkIfShouldUpdateAndYield();
196 
197  var propID = propMap[propNames[i]];
198  var resourcePropAlias = 'rp' + i;
199  selectSQL += ", " + resourcePropAlias + ".obj"; // rp_.obj
200  joinSQL += " LEFT OUTER JOIN resource_properties as " + resourcePropAlias +
201  " ON media_items.media_item_id = " +
202  resourcePropAlias + ".media_item_id" +
203  " AND " + resourcePropAlias + ".property_id = " + propID;
204  }
205 
206  // When finished the query will look like:
207  /* SELECT media_items.guid, media_items.content_mime_type,
208  * rp0.obj, rp1.obj... */
209  /* FROM media_items */
210  /* LEFT OUTER JOIN resource_properties as rp0
211  * ON media_items.media_item_id = rp0.media_item_id
212  * AND rp0.property_id = <property_id>
213  * LEFT OUTER JOIN resource_properties as rp1
214  * ON media_items.media_item_id = rp1.media_item_id
215  * AND rp1.property_id = <property_id> */
216  /* WHERE content_mime_type = '<contentType>'"
217  * AND media_items.is_list = '0' */
218 
219  let sql = selectSQL + " " +
220  fromSQL + " " +
221  joinSQL + " " +
222  whereSQL;
223  var selectPropertiesQuery = this.createMigrationQuery(this._library);
224  selectPropertiesQuery.addQuery(sql);
225 
226  // execute the query to retrieve the property data
227  var retval;
228  selectPropertiesQuery.execute(retval);
229 
230  /* The updateQuery will push all of the identities to the database.
231  * We add an update statement to updateQuery for each mediaitem as the
232  * identity for that mediaitem is calculate.
233  * All of the update statements are of a similar form, so we use the
234  * preparedUpdateStatement below. */
235  var updateQuery = this.createMigrationQuery(this._library);
236  updateQuery.addQuery("BEGIN");
237 
238  var preparedUpdateStatement = updateQuery.prepareQuery
239  ("UPDATE media_items SET metadata_hash_identity = ? WHERE guid = ?");
240 
241  /* Our last query retrieved the property data for each mediaitem, so we'll
242  * walk through that to form our string that will be hashed */
243  var propertyResultSet = selectPropertiesQuery.getResultObject();
244  var colCount = propertyResultSet.getColumnCount();
245  var rowCount = propertyResultSet.getRowCount();
246 
247  /* We know how many identities we will need to calculate, so make that the
248  * migration total. The dialog will be updated the next time we are
249  * told to yield in the following identity-calculation loop. */
250  this._total = rowCount;
251  this._statusText = "Migrating " + contentType + " files in 1.9 database to 1.10 database...";
252 
253  var idService = Cc["@songbirdnest.com/Songbird/IdentityService;1"]
254  .getService(Ci.sbIIdentityService);
255  for(let currentRow = 0; currentRow < rowCount; currentRow++) {
256  // Check if it's time for us to yield, and update the dialog if so
257  yield this.checkIfShouldUpdateAndYield();
258 
259  var guid = propertyResultSet.getRowCell(currentRow, 0);
260 
261  var hasHashableMetadata = false;
262  var propsToHash = [];
263 
264  // Form the string that will be hashed to form the identity
265  for (let currentCol = 1; currentCol < colCount; currentCol++) {
266  let propVal = propertyResultSet.getRowCell(currentRow, currentCol);
267 
268  if (propVal) {
269  propsToHash.push(propVal);
270  hasHashableMetadata = true;
271  }
272  else {
273  propsToHash.push("");
274  }
275  }
276 
277  /* If there was hashable metadata, calculate the identity now and add it
278  * to the update query */
279  if (hasHashableMetadata) {
280  var stringToHash = propsToHash.join(this.separator);
281  var identity = idService.hashString(stringToHash);
282  updateQuery.addPreparedStatement(preparedUpdateStatement);
283  updateQuery.bindStringParameter(0, identity);
284  updateQuery.bindStringParameter(1, guid);
285  }
286 
287  // This lets the dialog know that another item's identity was calculated
288  this._progress++;
289  }
290 
291  updateQuery.addQuery("commit");
292  updateQuery.execute(retval);
293 
294  /* We have finished handling this contentType. Mark this type as completed
295  * and check if all contentTypes have been completed. If no contentType
296  * still needs to be handled, mark the job as completed successfully and
297  * inform the dialog */
298  this._contentMigrations[contentType].completed = true;
299  for (var contentType in this._contentMigrations) {
300  if (!this._contentMigrations[contentType].completed) {
301  return;
302  }
303  }
304 
305  this._status = Ci.sbIJobProgress.STATUS_SUCCEEDED;
306  this.notifyJobProgressListeners();
307  },
308 
309  /* Takes an array of propertyNames and returns a map of those property_names
310  * to their corresponding property_id in the db */
311  getPropertyIDs: function sbLibraryMigration_getPropertyIDs(propertyNames) {
312  var propMap = {};
313  var sql = "SELECT property_name, property_id FROM properties WHERE " +
314  "property_name = '";
315  sql += propertyNames.join("' OR property_name = '");
316  sql += "'";
317 
318  // When finished the query will look like:
319  /* SELECT property_name, property_id
320  * FROM properties
321  * WHERE property_name = '<propertyNames[0]>'
322  * OR property_name = '<propetyNames[1]>'... */
323 
324  var query = this.createMigrationQuery(this._library);
325  query.addQuery(sql);
326 
327  var retval;
328  query.execute(retval);
329 
330  var resultSet = query.getResultObject();
331 
332  /* The rows of the result are of the form | property_name | property_id |
333  * for each of the property names that we were passed and could find.
334  * Fill the propMap with the appropriate
335  * property_name => property_id mappings. */
336  var propertyIDs = [];
337  var rowCount = resultSet.getRowCount();
338  for(let currentRow = 0; currentRow < rowCount; ++currentRow) {
339  propName = resultSet.getRowCell(currentRow, 0);
340  propId = resultSet.getRowCell(currentRow, 1);
341  propMap[propName] = propId;
342  }
343  return propMap;
344  },
345 
346  /* This utility method checks if it is time to yield, and if it is, we
347  * notify the dialog so that it will pick up the updated progress
348  * and then we yield so the dialog can update itself. */
349  checkIfShouldUpdateAndYield: function
350  sbLibraryMigration_checkIfShouldUpdateAndYield() {
351 
352  /* GeneratorThread handles controlling when we should yield, but we need to
353  * check it to know if that time has come. */
354  if (GeneratorThread.shouldYield()) {
355  this.notifyJobProgressListeners();
356  yield;
357  }
358  },
359 
360  /* We override these methods to report the current state in the
361  * progress dialog. */
362  get status() {
363  return this._status;
364  },
365  get progress() {
366  return this._progress;
367  },
368  get total() {
369  return this._total;
370  },
371 };
372 
373 //-----------------------------------------------------------------------------
374 // Module
375 //-----------------------------------------------------------------------------
376 function NSGetModule(compMgr, fileSpec) {
377  return XPCOMUtils.generateModule([
379  ]);
380 }
var total
function GeneratorThread(aEntryPoint)
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
function NSGetModule(compMgr, fileSpec)
const char * propName
return null
Definition: FeedWriter.js:1143
function completed(cancel)
sbDeviceFirmwareAutoCheckForUpdate prototype classID
_getSelectedPageStyle s i
sbAutoDownloader prototype _library