Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(417)

Side by Side Diff: ui/file_manager/gallery/js/gallery_item.js

Issue 1362873003: Gallery: keep exif info if image is saved to a copy. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix failed test case. Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ui/file_manager/gallery/js/gallery_data_model_unittest.js ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 /** 5 /**
6 * Object representing an image item (a photo). 6 * Object representing an image item (a photo).
7 * 7 *
8 * @param {!FileEntry} entry Image entry. 8 * @param {!FileEntry} entry Image entry.
9 * @param {!EntryLocation} locationInfo Entry location information. 9 * @param {!EntryLocation} locationInfo Entry location information.
10 * @param {MetadataItem} metadataItem 10 * @param {MetadataItem} metadataItem
(...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after
216 tryNext(10); 216 tryNext(10);
217 }; 217 };
218 218
219 /** 219 /**
220 * Returns true if the original format is writable format of Gallery. 220 * Returns true if the original format is writable format of Gallery.
221 * @return {boolean} True if the original format is writable format. 221 * @return {boolean} True if the original format is writable format.
222 */ 222 */
223 Gallery.Item.prototype.isWritableFormat = function() { 223 Gallery.Item.prototype.isWritableFormat = function() {
224 var type = FileType.getType(this.entry_); 224 var type = FileType.getType(this.entry_);
225 return type.type === 'image' && 225 return type.type === 'image' &&
226 (type.subtype === 'JPEG' || type.subtype === 'PNG') 226 (type.subtype === 'JPEG' || type.subtype === 'PNG');
227 }; 227 };
228 228
229 /** 229 /**
230 * Returns true if the entry of item is writable. 230 * Returns true if the entry of item is writable.
231 * @param {!VolumeManagerWrapper} volumeManager Volume manager. 231 * @param {!VolumeManagerWrapper} volumeManager Volume manager.
232 * @return {boolean} True if the entry of item is writable. 232 * @return {boolean} True if the entry of item is writable.
233 */ 233 */
234 Gallery.Item.prototype.isWritableFile = function(volumeManager) { 234 Gallery.Item.prototype.isWritableFile = function(volumeManager) {
235 return this.isWritableFormat() && 235 return this.isWritableFormat() &&
236 !this.locationInfo_.isReadOnly && 236 !this.locationInfo_.isReadOnly &&
(...skipping 18 matching lines...) Expand all
255 Gallery.Item.prototype.getCopyName = function(dirEntry) { 255 Gallery.Item.prototype.getCopyName = function(dirEntry) {
256 return new Promise(this.createCopyName_.bind( 256 return new Promise(this.createCopyName_.bind(
257 this, dirEntry, this.getNewMimeType_())); 257 this, dirEntry, this.getNewMimeType_()));
258 }; 258 };
259 259
260 /** 260 /**
261 * Writes the new item content to either the existing or a new file. 261 * Writes the new item content to either the existing or a new file.
262 * 262 *
263 * @param {!VolumeManagerWrapper} volumeManager Volume manager instance. 263 * @param {!VolumeManagerWrapper} volumeManager Volume manager instance.
264 * @param {!MetadataModel} metadataModel 264 * @param {!MetadataModel} metadataModel
265 * @param {DirectoryEntry} fallbackDir Fallback directory in case the current 265 * @param {!DirectoryEntry} fallbackDir Fallback directory in case the current
266 * directory is read only. 266 * directory is read only.
267 * @param {!HTMLCanvasElement} canvas Source canvas. 267 * @param {!HTMLCanvasElement} canvas Source canvas.
268 * @param {boolean} overwrite Set true to overwrite original if it's possible. 268 * @param {boolean} overwrite Set true to overwrite original if it's possible.
269 * @param {function(boolean)} callback Callback accepting true for success. 269 * @param {function(boolean)} callback Callback accepting true for success.
270 */ 270 */
271 Gallery.Item.prototype.saveToFile = function( 271 Gallery.Item.prototype.saveToFile = function(
272 volumeManager, metadataModel, fallbackDir, canvas, overwrite, callback) { 272 volumeManager, metadataModel, fallbackDir, canvas, overwrite, callback) {
273 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('SaveTime')); 273 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('SaveTime'));
274 var saveResultRecorded = false;
274 275
275 var name = this.getFileName(); 276 Promise.all([this.getEntryToWrite_(overwrite, fallbackDir, volumeManager),
276 var newMimeType = this.getNewMimeType_(); 277 this.getBlobForSave_(canvas, metadataModel)]).then(function(results) {
278 // Write content to the entry.
279 var fileEntry = results[0];
280 var blob = results[1];
277 281
278 var onSuccess = function(entry) { 282 // Create writer.
279 var locationInfo = volumeManager.getLocationInfo(entry); 283 return new Promise(function(resolve, reject) {
280 if (!locationInfo) { 284 fileEntry.createWriter(resolve, reject);
281 // Reuse old location info if it fails to obtain location info. 285 }).then(function(fileWriter) {
282 locationInfo = this.locationInfo_; 286 // Truncates the file to 0 byte if it overwrites existing file.
283 } 287 return new Promise(function(resolve, reject) {
284 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 1, 2); 288 if (util.isSameEntry(fileEntry, this.entry_)) {
285 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('SaveTime'));
286
287 this.entry_ = entry;
288 this.locationInfo_ = locationInfo;
289
290 // Updates the metadata.
291 metadataModel.notifyEntriesChanged([this.entry_]);
292 Promise.all([
293 metadataModel.get([entry], Gallery.PREFETCH_PROPERTY_NAMES),
294 new ThumbnailModel(metadataModel).get([entry])
295 ]).then(function(metadataLists) {
296 this.metadataItem_ = metadataLists[0][0];
297 this.thumbnailMetadataItem_ = metadataLists[1][0];
298 callback(true);
299 }.bind(this), function() {
300 callback(false);
301 });
302 }.bind(this);
303
304 var onError = function(error) {
305 console.error('Error saving from gallery', name, error);
306 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 0, 2);
307 if (callback)
308 callback(false);
309 };
310
311 var doSave = function(newFile, fileEntry) {
312 var blob;
313 var fileWriter;
314
315 metadataModel.get(
316 [fileEntry],
317 ['mediaMimeType', 'contentMimeType', 'ifd', 'exifLittleEndian']
318 ).then(function(metadataItems) {
319 // Create the blob of new image.
320 var metadataItem = metadataItems[0];
321 metadataItem.modificationTime = new Date();
322 metadataItem.mediaMimeType = newMimeType;
323 var metadataEncoder = ImageEncoder.encodeMetadata(
324 metadataItem, canvas, /* quality for thumbnail*/ 0.8);
325 // Contrary to what one might think 1.0 is not a good default. Opening
326 // and saving an typical photo taken with consumer camera increases
327 // its file size by 50-100%. Experiments show that 0.9 is much better.
328 // It shrinks some photos a bit, keeps others about the same size, but
329 // does not visibly lower the quality.
330 blob = ImageEncoder.getBlob(canvas, metadataEncoder, 0.9);
331 }.bind(this)).then(function() {
332 // Create writer.
333 return new Promise(function(fullfill, reject) {
334 fileEntry.createWriter(fullfill, reject);
335 });
336 }).then(function(writer) {
337 fileWriter = writer;
338
339 // Truncates the file to 0 byte if it overwrites.
340 return new Promise(function(fulfill, reject) {
341 if (!newFile) {
342 fileWriter.onerror = reject; 289 fileWriter.onerror = reject;
343 fileWriter.onwriteend = fulfill; 290 fileWriter.onwriteend = resolve;
344 fileWriter.truncate(0); 291 fileWriter.truncate(0);
345 } else { 292 } else {
346 fulfill(null); 293 resolve(null);
347 } 294 }
348 }); 295 }.bind(this)).then(function() {
349 }).then(function() { 296 // Writes the blob of new image.
350 // Writes the blob of new image. 297 return new Promise(function(resolve, reject) {
351 return new Promise(function(fulfill, reject) { 298 fileWriter.onerror = reject;
352 fileWriter.onerror = reject; 299 fileWriter.onwriteend = resolve;
353 fileWriter.onwriteend = fulfill; 300 fileWriter.write(blob);
354 fileWriter.write(blob); 301 });
355 }); 302 }).catch(function(error) {
356 }).then(onSuccess.bind(null, fileEntry))
357 .catch(function(error) {
358 onError(error);
359 if (fileWriter) {
360 // Disable all callbacks on the first error. 303 // Disable all callbacks on the first error.
361 fileWriter.onerror = null; 304 fileWriter.onerror = null;
362 fileWriter.onwriteend = null; 305 fileWriter.onwriteend = null;
306
307 return Promise.reject(error);
308 });
309 }.bind(this)).then(function() {
310 var locationInfo = volumeManager.getLocationInfo(fileEntry);
311 if (!locationInfo) {
312 // Reuse old location info if it fails to obtain location info.
313 locationInfo = this.locationInfo_;
363 } 314 }
364 });
365 }.bind(this);
366 315
367 var getFile = function(dir, newFile) { 316 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 1, 2);
368 dir.getFile(name, {create: newFile, exclusive: newFile}, 317 saveResultRecorded = true;
369 function(fileEntry) { 318 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('SaveTime'));
370 doSave(newFile, fileEntry);
371 }.bind(this), onError);
372 }.bind(this);
373 319
374 var checkExistence = function(dir) { 320 this.entry_ = fileEntry;
375 dir.getFile(name, {create: false, exclusive: false}, 321 this.locationInfo_ = locationInfo;
376 getFile.bind(null, dir, false /* existing file */),
377 getFile.bind(null, dir, true /* create new file */));
378 };
379 322
380 var saveToDir = function(dir) { 323 // Updates the metadata.
381 if (overwrite && 324 metadataModel.notifyEntriesChanged([this.entry_]);
382 !this.locationInfo_.isReadOnly && 325 Promise.all([
383 this.isWritableFormat()) { 326 metadataModel.get([this.entry_], Gallery.PREFETCH_PROPERTY_NAMES),
384 checkExistence(dir); 327 new ThumbnailModel(metadataModel).get([this.entry_])
385 return; 328 ]).then(function(metadataLists) {
386 } 329 this.metadataItem_ = metadataLists[0][0];
330 this.thumbnailMetadataItem_ = metadataLists[1][0];
331 callback(true);
332 }.bind(this), function() {
333 callback(false);
334 });
335 }.bind(this));
336 }.bind(this)).catch(function(error) {
337 console.error('Error saving from gallery', this.entry_.name, error);
387 338
388 this.createCopyName_(dir, newMimeType, function(copyName) { 339 if (!saveResultRecorded)
389 this.original_ = false; 340 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 0, 2);
390 name = copyName;
391 checkExistence(dir);
392 }.bind(this));
393 }.bind(this);
394 341
395 // Since in-place editing is not supported on MTP volume, Gallery.app handles 342 callback(false);
396 // MTP volume as read only volume. 343 }.bind(this));
397 if (this.locationInfo_.isReadOnly ||
398 GalleryUtil.isOnMTPVolume(this.entry_, volumeManager)) {
399 saveToDir(fallbackDir);
400 } else {
401 this.entry_.getParent(saveToDir, onError);
402 }
403 }; 344 };
404 345
405 /** 346 /**
347 * Returns file entry to write.
348 * @param {boolean} overwrite True to overwrite original file.
349 * @param {!DirectoryEntry} fallbackDirectory Directory to fallback if current
350 * directory is not writable.
351 * @param {!VolumeManagerWrapper} volumeManager
352 * @return {!Promise<!FileEntry>}
353 * @private
354 */
355 Gallery.Item.prototype.getEntryToWrite_ = function(
356 overwrite, fallbackDirectory, volumeManager) {
357 return new Promise(function(resolve, reject) {
358 // Since in-place editing is not supported on MTP volume, Gallery.app
359 // handles MTP volume as read only volume.
360 if (this.locationInfo_.isReadOnly ||
361 GalleryUtil.isOnMTPVolume(this.entry_, volumeManager)) {
362 resolve(fallbackDirectory);
363 } else {
364 this.entry_.getParent(resolve, reject);
365 }
366 }.bind(this)).then(function(directory) {
367 return new Promise(function(resolve) {
368 // Find file name.
369 if (overwrite &&
370 !this.locationInfo_.isReadOnly &&
371 this.isWritableFormat()) {
372 resolve(this.getFileName());
373 return;
374 }
375
376 this.createCopyName_(
377 directory, this.getNewMimeType_(), function(copyName) {
378 this.original_ = false;
379 resolve(copyName);
380 }.bind(this));
381 }.bind(this)).then(function(name) {
382 // Get File entry and return.
383 return new Promise(directory.getFile.bind(
384 directory, name, { create: true, exclusive: false }));
385 });
386 }.bind(this));
387 };
388
389 /**
390 * Returns blob to be saved.
391 * @param {!HTMLCanvasElement} canvas
392 * @param {!MetadataModel} metadataModel
393 * @return {!Promise<!Blob>}
394 * @private
395 */
396 Gallery.Item.prototype.getBlobForSave_ = function(canvas, metadataModel) {
397 return metadataModel.get(
398 [this.entry_],
399 ['mediaMimeType', 'contentMimeType', 'ifd', 'exifLittleEndian']
400 ).then(function(metadataItems) {
401 // Create the blob of new image.
402 var metadataItem = metadataItems[0];
403 metadataItem.modificationTime = new Date();
404 metadataItem.mediaMimeType = this.getNewMimeType_();
405 var metadataEncoder = ImageEncoder.encodeMetadata(
406 metadataItem, canvas, /* quality for thumbnail*/ 0.8);
407 // Contrary to what one might think 1.0 is not a good default. Opening
408 // and saving an typical photo taken with consumer camera increases
409 // its file size by 50-100%. Experiments show that 0.9 is much better.
410 // It shrinks some photos a bit, keeps others about the same size, but
411 // does not visibly lower the quality.
412 return ImageEncoder.getBlob(canvas, metadataEncoder, 0.9);
413 }.bind(this));
414 };
415
416 /**
406 * Renames the item. 417 * Renames the item.
407 * 418 *
408 * @param {string} displayName New display name (without the extension). 419 * @param {string} displayName New display name (without the extension).
409 * @return {!Promise} Promise fulfilled with when renaming completes, or 420 * @return {!Promise} Promise fulfilled with when renaming completes, or
410 * rejected with the error message. 421 * rejected with the error message.
411 */ 422 */
412 Gallery.Item.prototype.rename = function(displayName) { 423 Gallery.Item.prototype.rename = function(displayName) {
413 var newFileName = this.entry_.name.replace( 424 var newFileName = this.entry_.name.replace(
414 ImageUtil.getDisplayNameFromName(this.entry_.name), displayName); 425 ImageUtil.getDisplayNameFromName(this.entry_.name), displayName);
415 426
(...skipping 15 matching lines...) Expand all
431 return Promise.reject(str('GALLERY_FILE_EXISTS')); 442 return Promise.reject(str('GALLERY_FILE_EXISTS'));
432 }, function() { 443 }, function() {
433 return new Promise( 444 return new Promise(
434 this.entry_.moveTo.bind(this.entry_, parentDirectory, newFileName)); 445 this.entry_.moveTo.bind(this.entry_, parentDirectory, newFileName));
435 }.bind(this)); 446 }.bind(this));
436 }.bind(this)); 447 }.bind(this));
437 }.bind(this)).then(function(entry) { 448 }.bind(this)).then(function(entry) {
438 this.entry_ = entry; 449 this.entry_ = entry;
439 }.bind(this)); 450 }.bind(this));
440 }; 451 };
OLDNEW
« no previous file with comments | « ui/file_manager/gallery/js/gallery_data_model_unittest.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698