• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
13Gallery.Item = function(entry) {
14  this.entry_ = entry;
15  this.original_ = true;
16};
17
18/**
19 * @return {FileEntry} Image entry.
20 */
21Gallery.Item.prototype.getEntry = function() { return this.entry_ };
22
23/**
24 * @return {string} File name.
25 */
26Gallery.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 */
33Gallery.Item.prototype.isOriginal = function() { return this.original_ };
34
35// TODO: Localize?
36/**
37 * @type {string} Suffix for a edited copy file name.
38 */
39Gallery.Item.COPY_SIGNATURE = ' - Edited';
40
41/**
42 * Regular expression to match '... - Edited'.
43 * @type {RegExp}
44 */
45Gallery.Item.REGEXP_COPY_0 =
46    new RegExp('^(.+)' + Gallery.Item.COPY_SIGNATURE + '$');
47
48/**
49 * Regular expression to match '... - Edited (N)'.
50 * @type {RegExp}
51 */
52Gallery.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 */
62Gallery.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 */
123Gallery.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 */
200Gallery.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