home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Multimedia / Songbird / Songbird_2.0.0-2311_windows-i686-msvc8.exe / components / sbMigrate19to110pre0.addMetadataHashIdentity.js < prev    next >
Text File  |  2012-06-08  |  14KB  |  381 lines

  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.  
  47. function sbLibraryMigration()
  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([
  378.     sbLibraryMigration
  379.   ]);
  380. }
  381.