cros: Remove default pinned apps trial.
[chromium-blink-merge.git] / chrome / browser / resources / file_manager / background / js / volume_manager.js
blob35e94f2cd7e7f6efdc976902e3e5a40fb3c0abc1
1 // Copyright (c) 2012 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.
5 'use strict';
7 /**
8  * Represents each volume, such as "drive", "download directory", each "USB
9  * flush storage", or "mounted zip archive" etc.
10  *
11  * @param {util.VolumeType} volumeType The type of the volume.
12  * @param {string} mountPath Where the volume is mounted.
13  * @param {string} volumeId ID of the volume.
14  * @param {DirectoryEntry} root The root directory entry of this volume.
15  * @param {string} error The error if an error is found.
16  * @param {string} deviceType The type of device ('usb'|'sd'|'optical'|'mobile'
17  *     |'unknown') (as defined in chromeos/disks/disk_mount_manager.cc).
18  *     Can be null.
19  * @param {boolean} isReadOnly True if the volume is read only.
20  * @param {!{displayName:string, isCurrentProfile:boolean}} profile Profile
21  *     information.
22  * @constructor
23  */
24 function VolumeInfo(
25     volumeType,
26     mountPath,
27     volumeId,
28     root,
29     error,
30     deviceType,
31     isReadOnly,
32     profile) {
33   this.volumeType = volumeType;
34   // TODO(hidehiko): This should include FileSystem instance.
35   this.mountPath = mountPath;
36   this.volumeId = volumeId;
37   this.root = root;
39   // Note: This represents if the mounting of the volume is successfully done
40   // or not. (If error is empty string, the mount is successfully done).
41   // TODO(hidehiko): Rename to make this more understandable.
42   this.error = error;
43   this.deviceType = deviceType;
44   this.isReadOnly = isReadOnly;
45   this.profile = Object.freeze(profile);
47   // VolumeInfo is immutable.
48   Object.freeze(this);
51 /**
52  * Obtains a URL of the display root directory that users can see as a root.
53  * @return {string} URL of root entry.
54  */
55 VolumeInfo.prototype.getDisplayRootDirectoryURL = function() {
56   return this.root.toURL() +
57       (this.volumeType === util.VolumeType.DRIVE ? '/root' : '');
60 /**
61  * Obtains volume label.
62  * @return {string} Label for the volume.
63  */
64 VolumeInfo.prototype.getLabel = function() {
65   if (this.volumeType === util.VolumeType.DRIVE)
66     return str('DRIVE_DIRECTORY_LABEL');
67   else
68     return PathUtil.getFolderLabel(this.mountPath);
71 /**
72  * Utilities for volume manager implementation.
73  */
74 var volumeManagerUtil = {};
76 /**
77  * Throws an Error when the given error is not in util.VolumeError.
78  * @param {util.VolumeError} error Status string usually received from APIs.
79  */
80 volumeManagerUtil.validateError = function(error) {
81   for (var key in util.VolumeError) {
82     if (error === util.VolumeError[key])
83       return;
84   }
86   throw new Error('Invalid mount error: ' + error);
89 /**
90  * Returns the root entry of a volume mounted at mountPath.
91  *
92  * @param {string} mountPath The mounted path of the volume.
93  * @param {function(DirectoryEntry)} successCallback Called when the root entry
94  *     is found.
95  * @param {function(FileError)} errorCallback Called when an error is found.
96  * @private
97  */
98 volumeManagerUtil.getRootEntry_ = function(
99     mountPath, successCallback, errorCallback) {
100   // We always request FileSystem here, because requestFileSystem() grants
101   // permissions if necessary, especially for Drive File System at first mount
102   // time.
103   // Note that we actually need to request FileSystem after multi file system
104   // support, so this will be more natural code then.
105   chrome.fileBrowserPrivate.requestFileSystem(
106       'compatible',
107       function(fileSystem) {
108         // TODO(hidehiko): chrome.runtime.lastError should have error reason.
109         if (!fileSystem) {
110           errorCallback(util.createFileError(FileError.NOT_FOUND_ERR));
111           return;
112         }
114         fileSystem.root.getDirectory(
115             mountPath.substring(1),  // Strip leading '/'.
116             {create: false}, successCallback, errorCallback);
117       });
121  * Builds the VolumeInfo data from VolumeMetadata.
122  * @param {VolumeMetadata} volumeMetadata Metadata instance for the volume.
123  * @param {function(VolumeInfo)} callback Called on completion.
124  */
125 volumeManagerUtil.createVolumeInfo = function(volumeMetadata, callback) {
126   volumeManagerUtil.getRootEntry_(
127       volumeMetadata.mountPath,
128       function(entry) {
129         if (volumeMetadata.volumeType === util.VolumeType.DRIVE) {
130           // After file system is mounted, we "read" drive grand root
131           // entry at first. This triggers full feed fetch on background.
132           // Note: we don't need to handle errors here, because even if
133           // it fails, accessing to some path later will just become
134           // a fast-fetch and it re-triggers full-feed fetch.
135           entry.createReader().readEntries(
136               function() { /* do nothing */ },
137               function(error) {
138                 console.error(
139                     'Triggering full feed fetch is failed: ' +
140                         util.getFileErrorMnemonic(error.code));
141               });
142         }
143         callback(new VolumeInfo(
144             volumeMetadata.volumeType,
145             volumeMetadata.mountPath,
146             volumeMetadata.volumeId,
147             entry,
148             volumeMetadata.mountCondition,
149             volumeMetadata.deviceType,
150             volumeMetadata.isReadOnly,
151             volumeMetadata.profile));
152       },
153       function(fileError) {
154         console.error('Root entry is not found: ' +
155             volumeMetadata.mountPath + ', ' +
156             util.getFileErrorMnemonic(fileError.code));
157         callback(new VolumeInfo(
158             volumeMetadata.volumeType,
159             volumeMetadata.mountPath,
160             volumeMetadata.volumeId,
161             null,  // Root entry is not found.
162             volumeMetadata.mountCondition,
163             volumeMetadata.deviceType,
164             volumeMetadata.isReadOnly,
165             volumeMetadata.profile));
166       });
170  * The order of the volume list based on root type.
171  * @type {Array.<string>}
172  * @const
173  * @private
174  */
175 volumeManagerUtil.volumeListOrder_ = [
176   RootType.DRIVE, RootType.DOWNLOADS, RootType.ARCHIVE, RootType.REMOVABLE
180  * Compares mount paths to sort the volume list order.
181  * @param {string} mountPath1 The mount path for the first volume.
182  * @param {string} mountPath2 The mount path for the second volume.
183  * @return {number} 0 if mountPath1 and mountPath2 are same, -1 if VolumeInfo
184  *     mounted at mountPath1 should be listed before the one mounted at
185  *     mountPath2, otherwise 1.
186  */
187 volumeManagerUtil.compareMountPath = function(mountPath1, mountPath2) {
188   var order1 = volumeManagerUtil.volumeListOrder_.indexOf(
189       PathUtil.getRootType(mountPath1));
190   var order2 = volumeManagerUtil.volumeListOrder_.indexOf(
191       PathUtil.getRootType(mountPath2));
192   if (order1 !== order2)
193     return order1 < order2 ? -1 : 1;
195   if (mountPath1 !== mountPath2)
196     return mountPath1 < mountPath2 ? -1 : 1;
198   // The path is same.
199   return 0;
203  * The container of the VolumeInfo for each mounted volume.
204  * @constructor
205  */
206 function VolumeInfoList() {
207   /**
208    * Holds VolumeInfo instances.
209    * @type {cr.ui.ArrayDataModel}
210    * @private
211    */
212   this.model_ = new cr.ui.ArrayDataModel([]);
214   Object.freeze(this);
217 VolumeInfoList.prototype = {
218   get length() { return this.model_.length; }
222  * Adds the event listener to listen the change of volume info.
223  * @param {string} type The name of the event.
224  * @param {function(Event)} handler The handler for the event.
225  */
226 VolumeInfoList.prototype.addEventListener = function(type, handler) {
227   this.model_.addEventListener(type, handler);
231  * Removes the event listener.
232  * @param {string} type The name of the event.
233  * @param {function(Event)} handler The handler to be removed.
234  */
235 VolumeInfoList.prototype.removeEventListener = function(type, handler) {
236   this.model_.removeEventListener(type, handler);
240  * Adds the volumeInfo to the appropriate position. If there already exists,
241  * just replaces it.
242  * @param {VolumeInfo} volumeInfo The information of the new volume.
243  */
244 VolumeInfoList.prototype.add = function(volumeInfo) {
245   var index = this.findLowerBoundIndex_(volumeInfo.mountPath);
246   if (index < this.length &&
247       this.item(index).mountPath === volumeInfo.mountPath) {
248     // Replace the VolumeInfo.
249     this.model_.splice(index, 1, volumeInfo);
250   } else {
251     // Insert the VolumeInfo.
252     this.model_.splice(index, 0, volumeInfo);
253   }
257  * Removes the VolumeInfo of the volume mounted at mountPath.
258  * @param {string} mountPath The path to the location where the volume is
259  *     mounted.
260  */
261 VolumeInfoList.prototype.remove = function(mountPath) {
262   var index = this.findLowerBoundIndex_(mountPath);
263   if (index < this.length && this.item(index).mountPath === mountPath)
264     this.model_.splice(index, 1);
268  * Searches the information of the volume mounted at mountPath.
269  * @param {string} mountPath The path to the location where the volume is
270  *     mounted.
271  * @return {VolumeInfo} The volume's information, or null if not found.
272  */
273 VolumeInfoList.prototype.find = function(mountPath) {
274   var index = this.findLowerBoundIndex_(mountPath);
275   if (index < this.length && this.item(index).mountPath === mountPath)
276     return this.item(index);
278   // Not found.
279   return null;
283  * Searches the information of the volume that contains an item pointed by the
284  * path.
285  * @param {string} path Path pointing an entry on a volume.
286  * @return {VolumeInfo} The volume's information, or null if not found.
287  */
288 VolumeInfoList.prototype.findByPath = function(path) {
289   for (var i = 0; i < this.length; i++) {
290     var mountPath = this.item(i).mountPath;
291     if (path === mountPath || path.indexOf(mountPath + '/') === 0)
292       return this.item(i);
293   }
294   return null;
298  * @param {string} mountPath The mount path of searched volume.
299  * @return {number} The index of the volume if found, or the inserting
300  *     position of the volume.
301  * @private
302  */
303 VolumeInfoList.prototype.findLowerBoundIndex_ = function(mountPath) {
304   // Assuming the number of elements in the array data model is very small
305   // in most cases, use simple linear search, here.
306   for (var i = 0; i < this.length; i++) {
307     if (volumeManagerUtil.compareMountPath(
308             this.item(i).mountPath, mountPath) >= 0)
309       return i;
310   }
311   return this.length;
315  * @param {number} index The index of the volume in the list.
316  * @return {VolumeInfo} The VolumeInfo instance.
317  */
318 VolumeInfoList.prototype.item = function(index) {
319   return this.model_.item(index);
323  * VolumeManager is responsible for tracking list of mounted volumes.
325  * @constructor
326  * @extends {cr.EventTarget}
327  */
328 function VolumeManager() {
329   /**
330    * The list of archives requested to mount. We will show contents once
331    * archive is mounted, but only for mounts from within this filebrowser tab.
332    * @type {Object.<string, Object>}
333    * @private
334    */
335   this.requests_ = {};
337   /**
338    * The list of VolumeInfo instances for each mounted volume.
339    * @type {VolumeInfoList}
340    */
341   this.volumeInfoList = new VolumeInfoList();
343   // The status should be merged into VolumeManager.
344   // TODO(hidehiko): Remove them after the migration.
345   this.driveConnectionState_ = {
346     type: util.DriveConnectionType.OFFLINE,
347     reason: util.DriveConnectionReason.NO_SERVICE
348   };
350   chrome.fileBrowserPrivate.onDriveConnectionStatusChanged.addListener(
351       this.onDriveConnectionStatusChanged_.bind(this));
352   this.onDriveConnectionStatusChanged_();
356  * Invoked when the drive connection status is changed.
357  * @private_
358  */
359 VolumeManager.prototype.onDriveConnectionStatusChanged_ = function() {
360   chrome.fileBrowserPrivate.getDriveConnectionState(function(state) {
361     this.driveConnectionState_ = state;
362     cr.dispatchSimpleEvent(this, 'drive-connection-changed');
363   }.bind(this));
367  * Returns the drive connection state.
368  * @return {util.DriveConnectionType} Connection type.
369  */
370 VolumeManager.prototype.getDriveConnectionState = function() {
371   return this.driveConnectionState_;
375  * VolumeManager extends cr.EventTarget.
376  */
377 VolumeManager.prototype.__proto__ = cr.EventTarget.prototype;
380  * Time in milliseconds that we wait a response for. If no response on
381  * mount/unmount received the request supposed failed.
382  */
383 VolumeManager.TIMEOUT = 15 * 60 * 1000;
386  * Queue to run getInstance sequentially.
387  * @type {AsyncUtil.Queue}
388  * @private
389  */
390 VolumeManager.getInstanceQueue_ = new AsyncUtil.Queue();
393  * The singleton instance of VolumeManager. Initialized by the first invocation
394  * of getInstance().
395  * @type {VolumeManager}
396  * @private
397  */
398 VolumeManager.instance_ = null;
401  * Returns the VolumeManager instance asynchronously. If it is not created or
402  * under initialization, it will waits for the finish of the initialization.
403  * @param {function(VolumeManager)} callback Called with the VolumeManager
404  *     instance.
405  */
406 VolumeManager.getInstance = function(callback) {
407   VolumeManager.getInstanceQueue_.run(function(continueCallback) {
408     if (VolumeManager.instance_) {
409       callback(VolumeManager.instance_);
410       continueCallback();
411       return;
412     }
414     VolumeManager.instance_ = new VolumeManager();
415     VolumeManager.instance_.initialize_(function() {
416       callback(VolumeManager.instance_);
417       continueCallback();
418     });
419   });
423  * Initializes mount points.
424  * @param {function()} callback Called upon the completion of the
425  *     initialization.
426  * @private
427  */
428 VolumeManager.prototype.initialize_ = function(callback) {
429   chrome.fileBrowserPrivate.getVolumeMetadataList(function(volumeMetadataList) {
430     // Create VolumeInfo for each volume.
431     var group = new AsyncUtil.Group();
432     for (var i = 0; i < volumeMetadataList.length; i++) {
433       group.add(function(volumeMetadata, continueCallback) {
434         volumeManagerUtil.createVolumeInfo(
435             volumeMetadata,
436             function(volumeInfo) {
437               this.volumeInfoList.add(volumeInfo);
438               if (volumeMetadata.volumeType === util.VolumeType.DRIVE)
439                 this.onDriveConnectionStatusChanged_();
440               continueCallback();
441             }.bind(this));
442       }.bind(this, volumeMetadataList[i]));
443     }
445     // Then, finalize the initialization.
446     group.run(function() {
447       // Subscribe to the mount completed event when mount points initialized.
448       chrome.fileBrowserPrivate.onMountCompleted.addListener(
449           this.onMountCompleted_.bind(this));
450       callback();
451     }.bind(this));
452   }.bind(this));
456  * Event handler called when some volume was mounted or unmounted.
457  * @param {MountCompletedEvent} event Received event.
458  * @private
459  */
460 VolumeManager.prototype.onMountCompleted_ = function(event) {
461   if (event.eventType === 'mount') {
462     if (event.volumeMetadata.mountPath) {
463       var requestKey = this.makeRequestKey_(
464           'mount',
465           event.volumeMetadata.sourcePath);
467       var error = event.status === 'success' ? '' : event.status;
469       volumeManagerUtil.createVolumeInfo(
470           event.volumeMetadata,
471           function(volumeInfo) {
472             this.volumeInfoList.add(volumeInfo);
473             this.finishRequest_(requestKey, event.status, volumeInfo.mountPath);
475             if (volumeInfo.volumeType === util.VolumeType.DRIVE) {
476               // Update the network connection status, because until the
477               // drive is initialized, the status is set to not ready.
478               // TODO(hidehiko): The connection status should be migrated into
479               // VolumeMetadata.
480               this.onDriveConnectionStatusChanged_();
481             }
482           }.bind(this));
483     } else {
484       console.warn('No mount path.');
485       this.finishRequest_(requestKey, event.status);
486     }
487   } else if (event.eventType === 'unmount') {
488     var mountPath = event.volumeMetadata.mountPath;
489     var status = event.status;
490     if (status === util.VolumeError.PATH_UNMOUNTED) {
491       console.warn('Volume already unmounted: ', mountPath);
492       status = 'success';
493     }
494     var requestKey = this.makeRequestKey_('unmount', mountPath);
495     var requested = requestKey in this.requests_;
496     var volumeInfo = this.volumeInfoList.find(mountPath);
497     if (event.status === 'success' && !requested && volumeInfo) {
498       console.warn('Mounted volume without a request: ', mountPath);
499       var e = new Event('externally-unmounted');
500       // TODO(mtomasz): The mountPath field is deprecated. Remove it.
501       e.mountPath = mountPath;
502       e.volumeInfo = volumeInfo;
503       this.dispatchEvent(e);
504     }
505     this.finishRequest_(requestKey, status);
507     if (event.status === 'success')
508       this.volumeInfoList.remove(mountPath);
509   }
513  * Creates string to match mount events with requests.
514  * @param {string} requestType 'mount' | 'unmount'. TODO(hidehiko): Replace by
515  *     enum.
516  * @param {string} path Source path provided by API for mount request, or
517  *     mount path for unmount request.
518  * @return {string} Key for |this.requests_|.
519  * @private
520  */
521 VolumeManager.prototype.makeRequestKey_ = function(requestType, path) {
522   return requestType + ':' + path;
526  * @param {string} fileUrl File url to the archive file.
527  * @param {function(string)} successCallback Success callback.
528  * @param {function(util.VolumeError)} errorCallback Error callback.
529  */
530 VolumeManager.prototype.mountArchive = function(
531     fileUrl, successCallback, errorCallback) {
532   chrome.fileBrowserPrivate.addMount(fileUrl, function(sourcePath) {
533     console.info(
534         'Mount request: url=' + fileUrl + '; sourceUrl=' + sourcePath);
535     var requestKey = this.makeRequestKey_('mount', sourcePath);
536     this.startRequest_(requestKey, successCallback, errorCallback);
537   }.bind(this));
541  * Unmounts volume.
542  * @param {string} mountPath Volume mounted path.
543  * @param {function(string)} successCallback Success callback.
544  * @param {function(util.VolumeError)} errorCallback Error callback.
545  */
546 VolumeManager.prototype.unmount = function(mountPath,
547                                            successCallback,
548                                            errorCallback) {
549   var volumeInfo = this.volumeInfoList.find(mountPath);
550   if (!volumeInfo) {
551     errorCallback(util.VolumeError.NOT_MOUNTED);
552     return;
553   }
555   chrome.fileBrowserPrivate.removeMount(util.makeFilesystemUrl(mountPath));
556   var requestKey = this.makeRequestKey_('unmount', volumeInfo.mountPath);
557   this.startRequest_(requestKey, successCallback, errorCallback);
561  * Resolves the absolute path to its entry. Shouldn't be used outside of the
562  * Files app's initialization.
563  * @param {string} path The path to be resolved.
564  * @param {function(Entry)} successCallback Called with the resolved entry on
565  *     success.
566  * @param {function(FileError)} errorCallback Called on error.
567  */
568 VolumeManager.prototype.resolveAbsolutePath = function(
569     path, successCallback, errorCallback) {
570   // Make sure the path is in the mounted volume.
571   var volumeInfo = this.getVolumeInfo(path);
572   if (!volumeInfo || !volumeInfo.root) {
573     errorCallback(util.createFileError(FileError.NOT_FOUND_ERR));
574     return;
575   }
577   webkitResolveLocalFileSystemURL(
578       util.makeFilesystemUrl(path), successCallback, errorCallback);
582  * Obtains the information of the volume that containing an entry pointed by the
583  * specified path.
584  * TODO(hirono): Stop to use path to get a volume info.
586  * @param {string|Entry} target Path or Entry pointing anywhere on a volume.
587  * @return {VolumeInfo} The data about the volume.
588  */
589 VolumeManager.prototype.getVolumeInfo = function(target) {
590   if (typeof target === 'string')
591     return this.volumeInfoList.findByPath(target);
592   else if (util.isFakeEntry(target))
593     return this.getCurrentProfileVolumeInfo(util.VolumeType.DRIVE);
594   else
595     return this.volumeInfoList.findByPath(target.fullPath);
599  * Obtains a volume information from a file entry URL.
600  * TODO(hirono): Check a file system to find a volume.
602  * @param {string} url URL of entry.
603  * @return {VolumeInfo} Volume info.
604  */
605 VolumeManager.prototype.getVolumeInfoByURL = function(url) {
606   return this.getVolumeInfo(util.extractFilePath(url));
610  * Obtains a volume infomration of the current profile.
612  * @param {util.VolumeType} volumeType Volume type.
613  * @return {VolumeInfo} Volume info.
614  */
615 VolumeManager.prototype.getCurrentProfileVolumeInfo = function(volumeType) {
616   for (var i = 0; i < this.volumeInfoList.length; i++) {
617     var volumeInfo = this.volumeInfoList.item(i);
618     if (volumeInfo.profile.isCurrentProfile &&
619         volumeInfo.volumeType === volumeType)
620       return volumeInfo;
621   }
622   return null;
626  * Obtains location information from an entry.
628  * @param {Entry|Object} entry File or directory entry. It can be a fake entry.
629  * @return {EntryLocation} Location information.
630  */
631 VolumeManager.prototype.getLocationInfo = function(entry) {
632   if (util.isFakeEntry(entry)) {
633     return new EntryLocation(
634         // TODO(hirono): Specify currect volume.
635         this.getCurrentProfileVolumeInfo(RootType.DRIVE),
636         entry.rootType,
637         true /* the entry points a root directory. */);
638   } else {
639     return this.getLocationInfoByPath(entry.fullPath);
640   }
644  * Obtains location information from a path.
645  * TODO(hirono): Remove the method before introducing separate file system.
647  * @param {string} path Path.
648  * @return {EntryLocation} Location information.
649  */
650 VolumeManager.prototype.getLocationInfoByPath = function(path) {
651   var volumeInfo = this.volumeInfoList.findByPath(path);
652   return volumeInfo && PathUtil.getLocationInfo(volumeInfo, path);
656  * @param {string} key Key produced by |makeRequestKey_|.
657  * @param {function(string)} successCallback To be called when request finishes
658  *     successfully.
659  * @param {function(util.VolumeError)} errorCallback To be called when
660  *     request fails.
661  * @private
662  */
663 VolumeManager.prototype.startRequest_ = function(key,
664     successCallback, errorCallback) {
665   if (key in this.requests_) {
666     var request = this.requests_[key];
667     request.successCallbacks.push(successCallback);
668     request.errorCallbacks.push(errorCallback);
669   } else {
670     this.requests_[key] = {
671       successCallbacks: [successCallback],
672       errorCallbacks: [errorCallback],
674       timeout: setTimeout(this.onTimeout_.bind(this, key),
675                           VolumeManager.TIMEOUT)
676     };
677   }
681  * Called if no response received in |TIMEOUT|.
682  * @param {string} key Key produced by |makeRequestKey_|.
683  * @private
684  */
685 VolumeManager.prototype.onTimeout_ = function(key) {
686   this.invokeRequestCallbacks_(this.requests_[key],
687                                util.VolumeError.TIMEOUT);
688   delete this.requests_[key];
692  * @param {string} key Key produced by |makeRequestKey_|.
693  * @param {util.VolumeError|'success'} status Status received from the API.
694  * @param {string=} opt_mountPath Mount path.
695  * @private
696  */
697 VolumeManager.prototype.finishRequest_ = function(key, status, opt_mountPath) {
698   var request = this.requests_[key];
699   if (!request)
700     return;
702   clearTimeout(request.timeout);
703   this.invokeRequestCallbacks_(request, status, opt_mountPath);
704   delete this.requests_[key];
708  * @param {Object} request Structure created in |startRequest_|.
709  * @param {util.VolumeError|string} status If status === 'success'
710  *     success callbacks are called.
711  * @param {string=} opt_mountPath Mount path. Required if success.
712  * @private
713  */
714 VolumeManager.prototype.invokeRequestCallbacks_ = function(request, status,
715                                                            opt_mountPath) {
716   var callEach = function(callbacks, self, args) {
717     for (var i = 0; i < callbacks.length; i++) {
718       callbacks[i].apply(self, args);
719     }
720   };
721   if (status === 'success') {
722     callEach(request.successCallbacks, this, [opt_mountPath]);
723   } else {
724     volumeManagerUtil.validateError(status);
725     callEach(request.errorCallbacks, this, [status]);
726   }