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