| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 'use strict'; | 5 'use strict'; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Object representing an image item (a photo). | 8 * Object representing an image item (a photo). |
| 9 * | 9 * |
| 10 * @param {FileEntry} entry Image entry. | 10 * @param {FileEntry} entry Image entry. |
| 11 * @param {function():Promise} fethcedMediaProvider Function to provide the | 11 * @param {EntryLocation} locationInfo Entry location information. |
| 12 * fetchedMedia metadata. | 12 * @param {Object} metadata Metadata for the entry. |
| 13 * @param {MetadataCache} metadataCache Metadata cache instance. |
| 13 * @param {boolean} original Whether the entry is original or edited. | 14 * @param {boolean} original Whether the entry is original or edited. |
| 14 * @param {boolean} readonly Whether the entry is located at readonly directory | |
| 15 * or not. | |
| 16 * @constructor | 15 * @constructor |
| 17 */ | 16 */ |
| 18 Gallery.Item = function(entry, metadata, metadataCache, original, readonly) { | 17 Gallery.Item = function( |
| 18 entry, locationInfo, metadata, metadataCache, original) { |
| 19 /** | 19 /** |
| 20 * @type {FileEntry} | 20 * @type {FileEntry} |
| 21 * @private | 21 * @private |
| 22 */ | 22 */ |
| 23 this.entry_ = entry; | 23 this.entry_ = entry; |
| 24 | 24 |
| 25 /** | 25 /** |
| 26 * @type {EntryLocation} |
| 27 * @private |
| 28 */ |
| 29 this.locationInfo_ = locationInfo; |
| 30 |
| 31 /** |
| 26 * @type {Object} | 32 * @type {Object} |
| 27 * @private | 33 * @private |
| 28 */ | 34 */ |
| 29 this.metadata_ = Object.freeze(metadata); | 35 this.metadata_ = Object.freeze(metadata); |
| 30 | 36 |
| 31 /** | 37 /** |
| 32 * @type {MetadataCache} | 38 * @type {MetadataCache} |
| 33 * @private | 39 * @private |
| 34 */ | 40 */ |
| 35 this.metadataCache_ = metadataCache; | 41 this.metadataCache_ = metadataCache; |
| (...skipping 18 matching lines...) Expand all Loading... |
| 54 /** | 60 /** |
| 55 * Last accessed date to be used for selecting items whose cache are evicted. | 61 * Last accessed date to be used for selecting items whose cache are evicted. |
| 56 * @type {number} | 62 * @type {number} |
| 57 */ | 63 */ |
| 58 this.lastAccessed_ = Date.now(); | 64 this.lastAccessed_ = Date.now(); |
| 59 | 65 |
| 60 /** | 66 /** |
| 61 * @type {boolean} | 67 * @type {boolean} |
| 62 * @private | 68 * @private |
| 63 */ | 69 */ |
| 64 this.isReadOnly_ = readonly; | |
| 65 | |
| 66 /** | |
| 67 * @type {boolean} | |
| 68 * @private | |
| 69 */ | |
| 70 this.original_ = original; | 70 this.original_ = original; |
| 71 | 71 |
| 72 Object.seal(this); | 72 Object.seal(this); |
| 73 }; | 73 }; |
| 74 | 74 |
| 75 /** | 75 /** |
| 76 * @return {FileEntry} Image entry. | 76 * @return {FileEntry} Image entry. |
| 77 */ | 77 */ |
| 78 Gallery.Item.prototype.getEntry = function() { return this.entry_; }; | 78 Gallery.Item.prototype.getEntry = function() { return this.entry_; }; |
| 79 | 79 |
| 80 /** | 80 /** |
| 81 * @return {EntryLocation} Entry location information. |
| 82 */ |
| 83 Gallery.Item.prototype.getLocationInfo = function() { |
| 84 return this.locationInfo_; |
| 85 }; |
| 86 |
| 87 /** |
| 81 * @return {Object} Metadata. | 88 * @return {Object} Metadata. |
| 82 */ | 89 */ |
| 83 Gallery.Item.prototype.getMetadata = function() { return this.metadata_; }; | 90 Gallery.Item.prototype.getMetadata = function() { return this.metadata_; }; |
| 84 | 91 |
| 85 /** | 92 /** |
| 86 * Obtains the latest media metadata. | 93 * Obtains the latest media metadata. |
| 87 * | 94 * |
| 88 * This is a heavy operation since it forces to load the image data to obtain | 95 * This is a heavy operation since it forces to load the image data to obtain |
| 89 * the metadata. | 96 * the metadata. |
| 90 * @return {Promise} Promise to be fulfilled with fetched metadata. | 97 * @return {Promise} Promise to be fulfilled with fetched metadata. |
| (...skipping 26 matching lines...) Expand all Loading... |
| 117 Gallery.Item.prototype.getFileName = function() { | 124 Gallery.Item.prototype.getFileName = function() { |
| 118 return this.entry_.name; | 125 return this.entry_.name; |
| 119 }; | 126 }; |
| 120 | 127 |
| 121 /** | 128 /** |
| 122 * @return {boolean} True if this image has not been created in this session. | 129 * @return {boolean} True if this image has not been created in this session. |
| 123 */ | 130 */ |
| 124 Gallery.Item.prototype.isOriginal = function() { return this.original_; }; | 131 Gallery.Item.prototype.isOriginal = function() { return this.original_; }; |
| 125 | 132 |
| 126 /** | 133 /** |
| 127 * @return {boolean} Whther the item is located at a readonly directory. | |
| 128 */ | |
| 129 Gallery.Item.prototype.isReadOnly = function() { | |
| 130 return this.isReadOnly_; | |
| 131 }; | |
| 132 | |
| 133 /** | |
| 134 * Obtains the item is on the drive volume or not. | |
| 135 * @return {boolean} True if the item is on the drive volume. | |
| 136 */ | |
| 137 Gallery.Item.prototype.isOnDrive = function() { | |
| 138 return !!this.metadata_.drive; | |
| 139 }; | |
| 140 | |
| 141 /** | |
| 142 * Obtains the last accessed date. | 134 * Obtains the last accessed date. |
| 143 * @return {number} Last accessed date. | 135 * @return {number} Last accessed date. |
| 144 */ | 136 */ |
| 145 Gallery.Item.prototype.getLastAccessedDate = function() { | 137 Gallery.Item.prototype.getLastAccessedDate = function() { |
| 146 return this.lastAccessed_; | 138 return this.lastAccessed_; |
| 147 }; | 139 }; |
| 148 | 140 |
| 149 /** | 141 /** |
| 150 * Updates the last accessed date. | 142 * Updates the last accessed date. |
| 151 */ | 143 */ |
| 152 Gallery.Item.prototype.touch = function() { | 144 Gallery.Item.prototype.touch = function() { |
| 153 this.lastAccessed_ = Date.now(); | 145 this.lastAccessed_ = Date.now(); |
| 154 }; | 146 }; |
| 155 | 147 |
| 156 | |
| 157 // TODO: Localize? | 148 // TODO: Localize? |
| 158 /** | 149 /** |
| 159 * @type {string} Suffix for a edited copy file name. | 150 * @type {string} Suffix for a edited copy file name. |
| 160 */ | 151 */ |
| 161 Gallery.Item.COPY_SIGNATURE = ' - Edited'; | 152 Gallery.Item.COPY_SIGNATURE = ' - Edited'; |
| 162 | 153 |
| 163 /** | 154 /** |
| 164 * Regular expression to match '... - Edited'. | 155 * Regular expression to match '... - Edited'. |
| 165 * @type {RegExp} | 156 * @type {RegExp} |
| 166 */ | 157 */ |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 226 | 217 |
| 227 dirEntry.getFile(name + ext, {create: false, exclusive: false}, | 218 dirEntry.getFile(name + ext, {create: false, exclusive: false}, |
| 228 tryNext.bind(null, tries - 1), | 219 tryNext.bind(null, tries - 1), |
| 229 callback.bind(null, name + ext)); | 220 callback.bind(null, name + ext)); |
| 230 } | 221 } |
| 231 | 222 |
| 232 tryNext(10); | 223 tryNext(10); |
| 233 }; | 224 }; |
| 234 | 225 |
| 235 /** | 226 /** |
| 236 * Writes the new item content to the file. | 227 * Writes the new item content to either the existing or a new file. |
| 237 * | 228 * |
| 238 * @param {DirectoryEntry} fallbackDir If the entry is readonly, the edited | 229 * @param {VolumeManager} volumeManager Volume manager instance. |
| 239 * image is saved to the directory. | 230 * @param {string} fallbackDir Fallback directory in case the current directory |
| 240 * @param {boolean} overwrite True if overwrite, false if copy. | 231 * is read only. |
| 241 * @param {HTMLCanvasElement} canvas Source canvas. | 232 * @param {HTMLCanvasElement} canvas Source canvas. |
| 242 * @param {ImageEncoder.MetadataEncoder} metadataEncoder MetadataEncoder. | 233 * @param {ImageEncoder.MetadataEncoder} metadataEncoder MetadataEncoder. |
| 243 * @param {function(boolean)=} opt_callback Callback accepting true for success. | 234 * @param {function(boolean)=} opt_callback Callback accepting true for success. |
| 244 */ | 235 */ |
| 245 Gallery.Item.prototype.saveToFile = function( | 236 Gallery.Item.prototype.saveToFile = function( |
| 246 fallbackDir, overwrite, canvas, metadataEncoder, opt_callback) { | 237 volumeManager, fallbackDir, overwrite, canvas, metadataEncoder, |
| 238 opt_callback) { |
| 247 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('SaveTime')); | 239 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('SaveTime')); |
| 248 | 240 |
| 249 var name = this.getFileName(); | 241 var name = this.getFileName(); |
| 250 | 242 |
| 251 var onSuccess = function(entry) { | 243 var onSuccess = function(entry, locationInfo) { |
| 252 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 1, 2); | 244 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 1, 2); |
| 253 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('SaveTime')); | 245 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('SaveTime')); |
| 246 |
| 254 this.entry_ = entry; | 247 this.entry_ = entry; |
| 255 this.isReadOnly_ = false; | 248 this.locationInfo_ = locationInfo; |
| 249 |
| 256 this.metadataCache_.clear([this.entry_], 'fetchedMedia'); | 250 this.metadataCache_.clear([this.entry_], 'fetchedMedia'); |
| 257 if (opt_callback) | 251 if (opt_callback) |
| 258 opt_callback(true); | 252 opt_callback(true); |
| 259 }.bind(this); | 253 }.bind(this); |
| 260 | 254 |
| 261 function onError(error) { | 255 var onError = function(error) { |
| 262 console.error('Error saving from gallery', name, error); | 256 console.error('Error saving from gallery', name, error); |
| 263 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 0, 2); | 257 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 0, 2); |
| 264 if (opt_callback) | 258 if (opt_callback) |
| 265 opt_callback(false); | 259 opt_callback(false); |
| 266 } | 260 } |
| 267 | 261 |
| 268 function doSave(newFile, fileEntry) { | 262 var doSave = function(newFile, fileEntry) { |
| 269 fileEntry.createWriter(function(fileWriter) { | 263 fileEntry.createWriter(function(fileWriter) { |
| 270 function writeContent() { | 264 function writeContent() { |
| 271 fileWriter.onwriteend = onSuccess.bind(null, fileEntry); | 265 fileWriter.onwriteend = onSuccess.bind(null, fileEntry); |
| 272 fileWriter.write(ImageEncoder.getBlob(canvas, metadataEncoder)); | 266 fileWriter.write(ImageEncoder.getBlob(canvas, metadataEncoder)); |
| 273 } | 267 } |
| 274 fileWriter.onerror = function(error) { | 268 fileWriter.onerror = function(error) { |
| 275 onError(error); | 269 onError(error); |
| 276 // Disable all callbacks on the first error. | 270 // Disable all callbacks on the first error. |
| 277 fileWriter.onerror = null; | 271 fileWriter.onerror = null; |
| 278 fileWriter.onwriteend = null; | 272 fileWriter.onwriteend = null; |
| 279 }; | 273 }; |
| 280 if (newFile) { | 274 if (newFile) { |
| 281 writeContent(); | 275 writeContent(); |
| 282 } else { | 276 } else { |
| 283 fileWriter.onwriteend = writeContent; | 277 fileWriter.onwriteend = writeContent; |
| 284 fileWriter.truncate(0); | 278 fileWriter.truncate(0); |
| 285 } | 279 } |
| 286 }, onError); | 280 }, onError); |
| 287 } | 281 } |
| 288 | 282 |
| 289 function getFile(dir, newFile) { | 283 var getFile = function(dir, newFile) { |
| 290 dir.getFile(name, {create: newFile, exclusive: newFile}, | 284 dir.getFile(name, {create: newFile, exclusive: newFile}, |
| 291 doSave.bind(null, newFile), onError); | 285 function(fileEntry) { |
| 292 } | 286 var locationInfo = volumeManager.getLocationInfo(fileEntry); |
| 287 // If the volume is gone, then abort the saving operation. |
| 288 if (!locationInfo) { |
| 289 onError('NotFound'); |
| 290 return; |
| 291 } |
| 292 doSave(newFile, fileEntry, locationInfo); |
| 293 }.bind(this), onError); |
| 294 }.bind(this); |
| 293 | 295 |
| 294 function checkExistence(dir) { | 296 var checkExistence = function(dir) { |
| 295 dir.getFile(name, {create: false, exclusive: false}, | 297 dir.getFile(name, {create: false, exclusive: false}, |
| 296 getFile.bind(null, dir, false /* existing file */), | 298 getFile.bind(null, dir, false /* existing file */), |
| 297 getFile.bind(null, dir, true /* create new file */)); | 299 getFile.bind(null, dir, true /* create new file */)); |
| 298 } | 300 } |
| 299 | 301 |
| 300 var saveToDir = function(dir) { | 302 var saveToDir = function(dir) { |
| 301 if (overwrite && !this.isReadOnly_) { | 303 if (overwrite && !this.locationInfo_.isReadOnly) { |
| 302 checkExistence(dir); | 304 checkExistence(dir); |
| 303 } else { | 305 } else { |
| 304 this.createCopyName_(dir, function(copyName) { | 306 this.createCopyName_(dir, function(copyName) { |
| 305 this.original_ = false; | 307 this.original_ = false; |
| 306 name = copyName; | 308 name = copyName; |
| 307 checkExistence(dir); | 309 checkExistence(dir); |
| 308 }.bind(this)); | 310 }.bind(this)); |
| 309 } | 311 } |
| 310 }.bind(this); | 312 }.bind(this); |
| 311 | 313 |
| 312 if (this.isReadOnly_) { | 314 if (this.locationInfo_.isReadOnly) { |
| 313 saveToDir(fallbackDir); | 315 saveToDir(fallbackDir); |
| 314 } else { | 316 } else { |
| 315 this.entry_.getParent(saveToDir, onError); | 317 this.entry_.getParent(saveToDir, onError); |
| 316 } | 318 } |
| 317 }; | 319 }; |
| 318 | 320 |
| 319 /** | 321 /** |
| 320 * Renames the item. | 322 * Renames the item. |
| 321 * | 323 * |
| 322 * @param {string} displayName New display name (without the extension). | 324 * @param {string} displayName New display name (without the extension). |
| (...skipping 22 matching lines...) Expand all Loading... |
| 345 return Promise.reject(str('GALLERY_FILE_EXISTS')); | 347 return Promise.reject(str('GALLERY_FILE_EXISTS')); |
| 346 }, function() { | 348 }, function() { |
| 347 return new Promise( | 349 return new Promise( |
| 348 this.entry_.moveTo.bind(this.entry_, parentDirectory, newFileName)); | 350 this.entry_.moveTo.bind(this.entry_, parentDirectory, newFileName)); |
| 349 }.bind(this)); | 351 }.bind(this)); |
| 350 }.bind(this)); | 352 }.bind(this)); |
| 351 }.bind(this)).then(function(entry) { | 353 }.bind(this)).then(function(entry) { |
| 352 this.entry_ = entry; | 354 this.entry_ = entry; |
| 353 }.bind(this)); | 355 }.bind(this)); |
| 354 }; | 356 }; |
| OLD | NEW |