Home | History | Annotate | Download | only in js
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 'use strict';
      6 
      7 /**
      8  * Object representing an image item (a photo or a video).
      9  *
     10  * @param {FileEntry} entry Image entry.
     11  * @constructor
     12  */
     13 Gallery.Item = function(entry) {
     14   this.entry_ = entry;
     15   this.original_ = true;
     16 };
     17 
     18 /**
     19  * @return {FileEntry} Image entry.
     20  */
     21 Gallery.Item.prototype.getEntry = function() { return this.entry_ };
     22 
     23 /**
     24  * @return {string} File name.
     25  */
     26 Gallery.Item.prototype.getFileName = function() {
     27   return this.entry_.name;
     28 };
     29 
     30 /**
     31  * @return {boolean} True if this image has not been created in this session.
     32  */
     33 Gallery.Item.prototype.isOriginal = function() { return this.original_ };
     34 
     35 // TODO: Localize?
     36 /**
     37  * @type {string} Suffix for a edited copy file name.
     38  */
     39 Gallery.Item.COPY_SIGNATURE = ' - Edited';
     40 
     41 /**
     42  * Regular expression to match '... - Edited'.
     43  * @type {RegExp}
     44  */
     45 Gallery.Item.REGEXP_COPY_0 =
     46     new RegExp('^(.+)' + Gallery.Item.COPY_SIGNATURE + '$');
     47 
     48 /**
     49  * Regular expression to match '... - Edited (N)'.
     50  * @type {RegExp}
     51  */
     52 Gallery.Item.REGEXP_COPY_N =
     53     new RegExp('^(.+)' + Gallery.Item.COPY_SIGNATURE + ' \\((\\d+)\\)$');
     54 
     55 /**
     56  * Creates a name for an edited copy of the file.
     57  *
     58  * @param {Entry} dirEntry Entry.
     59  * @param {function} callback Callback.
     60  * @private
     61  */
     62 Gallery.Item.prototype.createCopyName_ = function(dirEntry, callback) {
     63   var name = this.getFileName();
     64 
     65   // If the item represents a file created during the current Gallery session
     66   // we reuse it for subsequent saves instead of creating multiple copies.
     67   if (!this.original_) {
     68     callback(name);
     69     return;
     70   }
     71 
     72   var ext = '';
     73   var index = name.lastIndexOf('.');
     74   if (index != -1) {
     75     ext = name.substr(index);
     76     name = name.substr(0, index);
     77   }
     78 
     79   if (!ext.match(/jpe?g/i)) {
     80     // Chrome can natively encode only two formats: JPEG and PNG.
     81     // All non-JPEG images are saved in PNG, hence forcing the file extension.
     82     ext = '.png';
     83   }
     84 
     85   function tryNext(tries) {
     86     // All the names are used. Let's overwrite the last one.
     87     if (tries == 0) {
     88       setTimeout(callback, 0, name + ext);
     89       return;
     90     }
     91 
     92     // If the file name contains the copy signature add/advance the sequential
     93     // number.
     94     var matchN = Gallery.Item.REGEXP_COPY_N.exec(name);
     95     var match0 = Gallery.Item.REGEXP_COPY_0.exec(name);
     96     if (matchN && matchN[1] && matchN[2]) {
     97       var copyNumber = parseInt(matchN[2], 10) + 1;
     98       name = matchN[1] + Gallery.Item.COPY_SIGNATURE + ' (' + copyNumber + ')';
     99     } else if (match0 && match0[1]) {
    100       name = match0[1] + Gallery.Item.COPY_SIGNATURE + ' (1)';
    101     } else {
    102       name += Gallery.Item.COPY_SIGNATURE;
    103     }
    104 
    105     dirEntry.getFile(name + ext, {create: false, exclusive: false},
    106         tryNext.bind(null, tries - 1),
    107         callback.bind(null, name + ext));
    108   }
    109 
    110   tryNext(10);
    111 };
    112 
    113 /**
    114  * Writes the new item content to the file.
    115  *
    116  * @param {Entry} overrideDir Directory to save to. If null, save to the same
    117  *   directory as the original.
    118  * @param {boolean} overwrite True if overwrite, false if copy.
    119  * @param {HTMLCanvasElement} canvas Source canvas.
    120  * @param {ImageEncoder.MetadataEncoder} metadataEncoder MetadataEncoder.
    121  * @param {function(boolean)=} opt_callback Callback accepting true for success.
    122  */
    123 Gallery.Item.prototype.saveToFile = function(
    124     overrideDir, overwrite, canvas, metadataEncoder, opt_callback) {
    125   ImageUtil.metrics.startInterval(ImageUtil.getMetricName('SaveTime'));
    126 
    127   var name = this.getFileName();
    128 
    129   var onSuccess = function(entry) {
    130     ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 1, 2);
    131     ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('SaveTime'));
    132     this.entry_ = entry;
    133     if (opt_callback) opt_callback(true);
    134   }.bind(this);
    135 
    136   function onError(error) {
    137     console.error('Error saving from gallery', name, error);
    138     ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 0, 2);
    139     if (opt_callback) opt_callback(false);
    140   }
    141 
    142   function doSave(newFile, fileEntry) {
    143     fileEntry.createWriter(function(fileWriter) {
    144       function writeContent() {
    145         fileWriter.onwriteend = onSuccess.bind(null, fileEntry);
    146         fileWriter.write(ImageEncoder.getBlob(canvas, metadataEncoder));
    147       }
    148       fileWriter.onerror = function(error) {
    149         onError(error);
    150         // Disable all callbacks on the first error.
    151         fileWriter.onerror = null;
    152         fileWriter.onwriteend = null;
    153       };
    154       if (newFile) {
    155         writeContent();
    156       } else {
    157         fileWriter.onwriteend = writeContent;
    158         fileWriter.truncate(0);
    159       }
    160     }, onError);
    161   }
    162 
    163   function getFile(dir, newFile) {
    164     dir.getFile(name, {create: newFile, exclusive: newFile},
    165         doSave.bind(null, newFile), onError);
    166   }
    167 
    168   function checkExistence(dir) {
    169     dir.getFile(name, {create: false, exclusive: false},
    170         getFile.bind(null, dir, false /* existing file */),
    171         getFile.bind(null, dir, true /* create new file */));
    172   }
    173 
    174   var saveToDir = function(dir) {
    175     if (overwrite) {
    176       checkExistence(dir);
    177     } else {
    178       this.createCopyName_(dir, function(copyName) {
    179         this.original_ = false;
    180         name = copyName;
    181         checkExistence(dir);
    182       }.bind(this));
    183     }
    184   }.bind(this);
    185 
    186   if (overrideDir) {
    187     saveToDir(overrideDir);
    188   } else {
    189     this.entry_.getParent(saveToDir, onError);
    190   }
    191 };
    192 
    193 /**
    194  * Renames the file.
    195  *
    196  * @param {string} displayName New display name (without the extension).
    197  * @param {function()} onSuccess Success callback.
    198  * @param {function()} onExists Called if the file with the new name exists.
    199  */
    200 Gallery.Item.prototype.rename = function(displayName, onSuccess, onExists) {
    201   var newFileName = this.entry_.name.replace(
    202       ImageUtil.getDisplayNameFromName(this.entry_.name), displayName);
    203 
    204   if (newFileName === this.entry_.name)
    205     return;
    206 
    207   var onRenamed = function(entry) {
    208     this.entry_ = entry;
    209     onSuccess();
    210   }.bind(this);
    211 
    212   var onError = function() {
    213     console.error(
    214         'Rename error: "' + this.entry_.name + '" to "' + newFileName + '"');
    215   };
    216 
    217   var moveIfDoesNotExist = function(parentDir) {
    218     parentDir.getFile(
    219         newFileName,
    220         {create: false, exclusive: false},
    221         onExists,
    222         function() {
    223           this.entry_.moveTo(parentDir, newFileName, onRenamed, onError);
    224         }.bind(this));
    225   }.bind(this);
    226 
    227   this.entry_.getParent(moveIfDoesNotExist, onError);
    228 };
    229