Commit 867c55d7 authored by Stuart Langley's avatar Stuart Langley Committed by Commit Bot

Add a directory entry for "Computers" in the directory tree.

Add support for /drive/Computers in volume_info and directory_tree,
for displaying a users backed up Computers, if they have any. The
logic here is very similar to the logic for team drives, where we will
only display the '/Computers' entry if there is a sub-directory under
it. Hence a lot of the code is cargo culted from the team drives
implementation.

Potentially come back and refactor the common code between Computers
and Team Drives once this stabilizes.

Bug: 884020
Change-Id: I1ebf52ba64e5cb5bd809b64cc52bf182e3d11f19
Reviewed-on: https://chromium-review.googlesource.com/c/1255084
Commit-Queue: Stuart Langley <slangley@chromium.org>
Reviewed-by: default avatarBrian White <bcwhite@chromium.org>
Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Reviewed-by: default avatarLuciano Pacheco <lucmult@chromium.org>
Cr-Commit-Position: refs/heads/master@{#600650}
parent 27df0590
...@@ -47,6 +47,9 @@ ...@@ -47,6 +47,9 @@
<message name="IDS_FILE_BROWSER_DRIVE_TEAM_DRIVES_LABEL" desc="A label for the 'Team Drives' collection of Google Drive."> <message name="IDS_FILE_BROWSER_DRIVE_TEAM_DRIVES_LABEL" desc="A label for the 'Team Drives' collection of Google Drive.">
Team Drives Team Drives
</message> </message>
<message name="IDS_FILE_BROWSER_DRIVE_COMPUTERS_LABEL" desc="A label for the 'Computers' collection of Google Drive.">
Computers
</message>
<message name="IDS_FILE_BROWSER_DRIVE_SHARED_WITH_ME_COLLECTION_LABEL" desc="A label for the 'shared with me' collection of Google Drive."> <message name="IDS_FILE_BROWSER_DRIVE_SHARED_WITH_ME_COLLECTION_LABEL" desc="A label for the 'shared with me' collection of Google Drive.">
Shared with me Shared with me
</message> </message>
......
5bab14e41979f9abcb3430747ff722cbee84b069
\ No newline at end of file
...@@ -131,6 +131,7 @@ void AddStringsForDrive(base::DictionaryValue* dict) { ...@@ -131,6 +131,7 @@ void AddStringsForDrive(base::DictionaryValue* dict) {
SET_STRING("DRIVE_MY_DRIVE_LABEL", IDS_FILE_BROWSER_DRIVE_MY_DRIVE_LABEL); SET_STRING("DRIVE_MY_DRIVE_LABEL", IDS_FILE_BROWSER_DRIVE_MY_DRIVE_LABEL);
SET_STRING("DRIVE_TEAM_DRIVES_LABEL", SET_STRING("DRIVE_TEAM_DRIVES_LABEL",
IDS_FILE_BROWSER_DRIVE_TEAM_DRIVES_LABEL); IDS_FILE_BROWSER_DRIVE_TEAM_DRIVES_LABEL);
SET_STRING("DRIVE_COMPUTERS_LABEL", IDS_FILE_BROWSER_DRIVE_COMPUTERS_LABEL);
SET_STRING("DRIVE_NOT_REACHED", IDS_FILE_BROWSER_DRIVE_NOT_REACHED); SET_STRING("DRIVE_NOT_REACHED", IDS_FILE_BROWSER_DRIVE_NOT_REACHED);
SET_STRING("DRIVE_OFFLINE_COLLECTION_LABEL", SET_STRING("DRIVE_OFFLINE_COLLECTION_LABEL",
IDS_FILE_BROWSER_DRIVE_OFFLINE_COLLECTION_LABEL); IDS_FILE_BROWSER_DRIVE_OFFLINE_COLLECTION_LABEL);
......
...@@ -33126,6 +33126,18 @@ uploading your change for review. ...@@ -33126,6 +33126,18 @@ uploading your change for review.
</summary> </summary>
</histogram> </histogram>
<histogram name="FileBrowser.ComputersCount" units="Computers"
expires_after="M76">
<owner>slangley@chromium.org</owner>
<owner>weifangsun@chromium.org</owner>
<summary>
Chrome OS File Browser: number of Computers a user has available in the
Files app. Computed every time the File Browser is opened (including file
picker dialogs). NOTE: This data is biased towards users that use the Files
App more often.
</summary>
</histogram>
<histogram name="FileBrowser.Create" enum="FileDialogType"> <histogram name="FileBrowser.Create" enum="FileDialogType">
<owner>sashab@chromium.org</owner> <owner>sashab@chromium.org</owner>
<owner>slangley@chromium.org</owner> <owner>slangley@chromium.org</owner>
...@@ -31,6 +31,13 @@ VolumeInfo.prototype.displayRoot; ...@@ -31,6 +31,13 @@ VolumeInfo.prototype.displayRoot;
*/ */
VolumeInfo.prototype.teamDriveDisplayRoot; VolumeInfo.prototype.teamDriveDisplayRoot;
/**
* The display root path of Computers directory. It is null before finishing
* to resolve the entry. Valid only for Drive volume.
* @type {DirectoryEntry}
*/
VolumeInfo.prototype.computersDisplayRoot;
/** /**
* The volume's fake entries such as Recent, Offline, Shared with me, etc... * The volume's fake entries such as Recent, Offline, Shared with me, etc...
* in Google Drive. * in Google Drive.
......
...@@ -37,7 +37,9 @@ function EntryLocationImpl(volumeInfo, rootType, isRootEntry, isReadOnly) { ...@@ -37,7 +37,9 @@ function EntryLocationImpl(volumeInfo, rootType, isRootEntry, isReadOnly) {
this.rootType === VolumeManagerCommon.RootType.DRIVE_RECENT || this.rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
this.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE || this.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE ||
this.rootType === VolumeManagerCommon.RootType.TEAM_DRIVES_GRAND_ROOT || this.rootType === VolumeManagerCommon.RootType.TEAM_DRIVES_GRAND_ROOT ||
this.rootType === VolumeManagerCommon.RootType.TEAM_DRIVE; this.rootType === VolumeManagerCommon.RootType.TEAM_DRIVE ||
this.rootType === VolumeManagerCommon.RootType.COMPUTERS_GRAND_ROOT ||
this.rootType === VolumeManagerCommon.RootType.COMPUTER;
/** @override */ /** @override */
this.isReadOnly = isReadOnly; this.isReadOnly = isReadOnly;
......
...@@ -47,6 +47,7 @@ function VolumeInfoImpl( ...@@ -47,6 +47,7 @@ function VolumeInfoImpl(
this.label_ = label; this.label_ = label;
this.displayRoot_ = null; this.displayRoot_ = null;
this.teamDriveDisplayRoot_ = null; this.teamDriveDisplayRoot_ = null;
this.computersDisplayRoot_ = null;
/** /**
* @type {FilesAppEntry} an entry to be used as prefix of this volume on * @type {FilesAppEntry} an entry to be used as prefix of this volume on
...@@ -125,6 +126,14 @@ VolumeInfoImpl.prototype = /** @struct */ { ...@@ -125,6 +126,14 @@ VolumeInfoImpl.prototype = /** @struct */ {
get teamDriveDisplayRoot() { get teamDriveDisplayRoot() {
return this.teamDriveDisplayRoot_; return this.teamDriveDisplayRoot_;
}, },
/**
* @return {DirectoryEntry} The display root path of Computers directory.
* It is null before finishing to resolve the entry. Valid only for Drive
* volume.
*/
get computersDisplayRoot() {
return this.computersDisplayRoot_;
},
/** /**
* @return {Object<!FakeEntry>} Fake entries. * @return {Object<!FakeEntry>} Fake entries.
*/ */
...@@ -259,6 +268,31 @@ VolumeInfoImpl.prototype.resolveTeamDrivesRoot_ = function() { ...@@ -259,6 +268,31 @@ VolumeInfoImpl.prototype.resolveTeamDrivesRoot_ = function() {
}); });
}; };
/**
* Sets |computersDisplayRoot_| if Computers are enabled.
*
* If Computers are not enabled, resolveFileSystemUrl_ will return a
* 'NotFoundError' which will be caught here. Any other errors will be rethrown.
*
* The return value will resolve once this operation is complete.
* @return {!Promise<void>}
*/
VolumeInfoImpl.prototype.resolveComputersRoot_ = function() {
return VolumeInfoImpl
.resolveFileSystemUrl_(
this.fileSystem_.root.toURL() +
VolumeManagerCommon.COMPUTERS_DIRECTORY_NAME)
.then(
(computersRoot) => {
this.computersDisplayRoot_ = computersRoot;
},
(error) => {
if (error.name != 'NotFoundError') {
throw error;
}
});
};
/** /**
* @override * @override
*/ */
...@@ -281,7 +315,8 @@ VolumeInfoImpl.prototype.resolveDisplayRoot = function(opt_onSuccess, ...@@ -281,7 +315,8 @@ VolumeInfoImpl.prototype.resolveDisplayRoot = function(opt_onSuccess,
Promise Promise
.all([ .all([
VolumeInfoImpl.resolveFileSystemUrl_(displayRootURL), VolumeInfoImpl.resolveFileSystemUrl_(displayRootURL),
this.resolveTeamDrivesRoot_() this.resolveTeamDrivesRoot_(),
this.resolveComputersRoot_(),
]) ])
.then(([root]) => { .then(([root]) => {
return root; return root;
......
...@@ -1148,6 +1148,9 @@ util.getRootTypeLabel = function(locationInfo) { ...@@ -1148,6 +1148,9 @@ util.getRootTypeLabel = function(locationInfo) {
// By this reason, we return the label of the Team Drives grand root here. // By this reason, we return the label of the Team Drives grand root here.
case VolumeManagerCommon.RootType.TEAM_DRIVES_GRAND_ROOT: case VolumeManagerCommon.RootType.TEAM_DRIVES_GRAND_ROOT:
return str('DRIVE_TEAM_DRIVES_LABEL'); return str('DRIVE_TEAM_DRIVES_LABEL');
case VolumeManagerCommon.RootType.COMPUTER:
case VolumeManagerCommon.RootType.COMPUTERS_GRAND_ROOT:
return str('DRIVE_COMPUTERS_LABEL');
case VolumeManagerCommon.RootType.DRIVE_OFFLINE: case VolumeManagerCommon.RootType.DRIVE_OFFLINE:
return str('DRIVE_OFFLINE_COLLECTION_LABEL'); return str('DRIVE_OFFLINE_COLLECTION_LABEL');
case VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME: case VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME:
......
...@@ -41,6 +41,9 @@ DirectoryItemTreeBaseMethods.getItemByEntry = function(entry) { ...@@ -41,6 +41,9 @@ DirectoryItemTreeBaseMethods.getItemByEntry = function(entry) {
if (util.isTeamDriveEntry(entry) && item instanceof DriveVolumeItem) if (util.isTeamDriveEntry(entry) && item instanceof DriveVolumeItem)
return item.getItemByEntry(entry); return item.getItemByEntry(entry);
if (util.isComputersEntry(entry) && item instanceof DriveVolumeItem)
return item.getItemByEntry(entry);
if (util.isDescendantEntry(item.entry, entry)) if (util.isDescendantEntry(item.entry, entry))
return item.getItemByEntry(entry); return item.getItemByEntry(entry);
} }
...@@ -70,6 +73,11 @@ DirectoryItemTreeBaseMethods.searchAndSelectByEntry = function(entry) { ...@@ -70,6 +73,11 @@ DirectoryItemTreeBaseMethods.searchAndSelectByEntry = function(entry) {
return true; return true;
} }
if (util.isComputersEntry(entry) && item instanceof DriveVolumeItem) {
item.selectByEntry(entry);
return true;
}
if (util.isDescendantEntry(item.entry, entry) || if (util.isDescendantEntry(item.entry, entry) ||
util.isSameEntry(item.entry, entry)) { util.isSameEntry(item.entry, entry)) {
item.selectByEntry(entry); item.selectByEntry(entry);
...@@ -222,6 +230,9 @@ DirectoryItem.prototype = { ...@@ -222,6 +230,9 @@ DirectoryItem.prototype = {
locationInfo.rootType === locationInfo.rootType ===
VolumeManagerCommon.RootType.TEAM_DRIVES_GRAND_ROOT || VolumeManagerCommon.RootType.TEAM_DRIVES_GRAND_ROOT ||
locationInfo.rootType === VolumeManagerCommon.RootType.TEAM_DRIVE || locationInfo.rootType === VolumeManagerCommon.RootType.TEAM_DRIVE ||
locationInfo.rootType ===
VolumeManagerCommon.RootType.COMPUTERS_GRAND_ROOT ||
locationInfo.rootType === VolumeManagerCommon.RootType.COMPUTER ||
locationInfo.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE || locationInfo.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE ||
locationInfo.rootType === locationInfo.rootType ===
VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME || VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
...@@ -1043,7 +1054,7 @@ DriveVolumeItem.prototype.handleClick = function(e) { ...@@ -1043,7 +1054,7 @@ DriveVolumeItem.prototype.handleClick = function(e) {
* Since we don't currently support any functionality with just the grand root * Since we don't currently support any functionality with just the grand root
* (e.g. you can't create a new team drive from the root yet), remove/don't * (e.g. you can't create a new team drive from the root yet), remove/don't
* create the grand root so it can't be reached via keyboard. * create the grand root so it can't be reached via keyboard.
* If there is at least one Team Drive, add/show the Team Drives trand root. * If there is at least one Team Drive, add/show the Team Drives grand root.
* *
* @return {!Promise<SubDirectoryItem>} Resolved with Team Drive Grand Root * @return {!Promise<SubDirectoryItem>} Resolved with Team Drive Grand Root
* SubDirectoryItem instance, or undefined when it shouldn't exist. * SubDirectoryItem instance, or undefined when it shouldn't exist.
...@@ -1072,7 +1083,7 @@ DriveVolumeItem.prototype.createTeamDrivesGrandRoot_ = function() { ...@@ -1072,7 +1083,7 @@ DriveVolumeItem.prototype.createTeamDrivesGrandRoot_ = function() {
metrics.recordSmallCount('TeamDrivesCount', results.length); metrics.recordSmallCount('TeamDrivesCount', results.length);
// Only create grand root if there is at least 1 child/result. // Only create grand root if there is at least 1 child/result.
if (results.length) { if (results.length) {
if (index) { if (index !== undefined) {
this.items[index].hidden = false; this.items[index].hidden = false;
resolve(this.items[index]); resolve(this.items[index]);
return; return;
...@@ -1102,6 +1113,76 @@ DriveVolumeItem.prototype.createTeamDrivesGrandRoot_ = function() { ...@@ -1102,6 +1113,76 @@ DriveVolumeItem.prototype.createTeamDrivesGrandRoot_ = function() {
}); });
}; };
/**
* Creates Computers root if there is any computer. If there is no computer,
* then it removes the root.
*
* Since we don't currently support any functionality with just the grand root
* (e.g. you can't create a new computer from the root yet), remove/don't
* create the grand root so it can't be reached via keyboard.
* If there is at least one Computer, add/show the Computer grand root.
*
* @return {!Promise<SubDirectoryItem>} Resolved with Computer Grand Root
* SubDirectoryItem instance, or undefined when it shouldn't exist.
* @private
*/
DriveVolumeItem.prototype.createComputersGrandRoot_ = function() {
return new Promise(resolve => {
const computerGrandRoot = this.volumeInfo_.computersDisplayRoot;
if (!computerGrandRoot) {
// Computer is disabled.
resolve();
return;
}
let index;
for (let i = 0; i < this.items.length; i++) {
const entry = this.items[i] && this.items[i].entry;
if (entry && util.isSameEntry(entry, computerGrandRoot)) {
index = i;
break;
}
}
const reader = computerGrandRoot.createReader();
reader.readEntries((results) => {
metrics.recordSmallCount('ComputersCount', results.length);
// Only create grand root if there is at least 1 child/result.
if (results.length) {
if (index !== undefined) {
this.items[index].hidden = false;
resolve(this.items[index]);
return;
}
// Create if it doesn't exist yet.
const label = util.getEntryLabel(
this.parentTree_.volumeManager_.getLocationInfo(
computerGrandRoot),
computerGrandRoot) ||
'';
const item = new SubDirectoryItem(
label, computerGrandRoot, this, this.parentTree_);
// We want to show "Computers" after "Team Drives", the
// computersIndexPosition_() helper function will work out the correct
// index to place "Computers" at.
const position = this.computersIndexPosition_();
this.addAt(item, position);
item.updateSubDirectories(false);
resolve(item);
return;
} else {
// When there is no computer, the grand root should be removed.
if (index && this.items[index].parentItem) {
this.items[index].parentItem.remove(this.items[index]);
}
resolve();
return;
}
});
});
};
/** /**
* Retrieves the latest subdirectories and update them on the tree. * Retrieves the latest subdirectories and update them on the tree.
* @param {boolean} recursive True if the update is recursively. * @param {boolean} recursive True if the update is recursively.
...@@ -1118,6 +1199,11 @@ DriveVolumeItem.prototype.updateSubDirectories = function(recursive) { ...@@ -1118,6 +1199,11 @@ DriveVolumeItem.prototype.updateSubDirectories = function(recursive) {
entries.push(teamDrivesDisplayRoot); entries.push(teamDrivesDisplayRoot);
} }
var computersDisplayRoot = this.volumeInfo_.computersDisplayRoot;
if (!!computersDisplayRoot) {
entries.push(computersDisplayRoot);
}
// Drive volume has children including fake entries (offline, recent, ...) // Drive volume has children including fake entries (offline, recent, ...)
var fakeEntries = []; var fakeEntries = [];
if (this.parentTree_.fakeEntriesVisible_) { if (this.parentTree_.fakeEntriesVisible_) {
...@@ -1137,6 +1223,8 @@ DriveVolumeItem.prototype.updateSubDirectories = function(recursive) { ...@@ -1137,6 +1223,8 @@ DriveVolumeItem.prototype.updateSubDirectories = function(recursive) {
const entry = entries[i]; const entry = entries[i];
if (entry === teamDrivesDisplayRoot) { if (entry === teamDrivesDisplayRoot) {
this.createTeamDrivesGrandRoot_(); this.createTeamDrivesGrandRoot_();
} else if (entry === computersDisplayRoot) {
this.createComputersGrandRoot_();
} else { } else {
const label = const label =
util.getEntryLabel( util.getEntryLabel(
...@@ -1162,21 +1250,30 @@ DriveVolumeItem.prototype.updateSubDirectories = function(recursive) { ...@@ -1162,21 +1250,30 @@ DriveVolumeItem.prototype.updateSubDirectories = function(recursive) {
* @override * @override
*/ */
DriveVolumeItem.prototype.updateItemByEntry = function(changedDirectoryEntry) { DriveVolumeItem.prototype.updateItemByEntry = function(changedDirectoryEntry) {
// The first item is My Drive, and the second item is Team Drives.
// Keep in sync with |fixedEntries| in |updateSubDirectories|.
const isTeamDriveChild = util.isTeamDriveEntry(changedDirectoryEntry); const isTeamDriveChild = util.isTeamDriveEntry(changedDirectoryEntry);
const index = isTeamDriveChild ? 1 : 0;
// If Team Drive grand root has been removed and we receive an update for an // If Team Drive grand root has been removed and we receive an update for an
// team drive, we need to create the Team Drive grand root. // team drive, we need to create the Team Drive grand root.
if (isTeamDriveChild) { if (isTeamDriveChild) {
this.createTeamDrivesGrandRoot_().then(teamDriveGranRootItem => { this.createTeamDrivesGrandRoot_().then(teamDriveGrandRootItem => {
if (teamDriveGranRootItem) if (teamDriveGrandRootItem)
this.items[index].updateItemByEntry(changedDirectoryEntry); teamDriveGrandRootItem.updateItemByEntry(changedDirectoryEntry);
}); });
} else { return;
this.items[index].updateItemByEntry(changedDirectoryEntry); }
const isComputersChild = util.isComputersEntry(changedDirectoryEntry);
// If Computers grand root has been removed and we receive an update for an
// computer, we need to create the Computers grand root.
if (isComputersChild) {
this.createComputersGrandRoot_().then(computersGrandRootItem => {
if (computersGrandRootItem)
computersGrandRootItem.updateItemByEntry(changedDirectoryEntry);
});
return;
} }
// Must be under "My Drive", which is always the first item.
this.items[0].updateItemByEntry(changedDirectoryEntry);
}; };
/** /**
...@@ -1190,6 +1287,27 @@ DriveVolumeItem.prototype.selectByEntry = function(entry) { ...@@ -1190,6 +1287,27 @@ DriveVolumeItem.prototype.selectByEntry = function(entry) {
this.searchAndSelectByEntry(entry); this.searchAndSelectByEntry(entry);
}; };
/**
* Return the index where we want to display the "Computers" root.
* @private
*/
DriveVolumeItem.prototype.computersIndexPosition_ = function() {
// We want the order to be
// - My Drive
// - Team Drives (if the user has any)
// - Computers (if the user has any)
// So if the user has team drives we want index position 2, otherwise index
// position 1.
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if (!item.entry)
continue;
if (util.isTeamDriveEntry(item.entry))
return 2;
}
return 1;
};
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// ShortcutItem // ShortcutItem
......
...@@ -16,7 +16,7 @@ const mockVolumeManager = new MockVolumeManager(); ...@@ -16,7 +16,7 @@ const mockVolumeManager = new MockVolumeManager();
mockVolumeManager mockVolumeManager
.getCurrentProfileVolumeInfo(VolumeManagerCommon.VolumeType.DRIVE) .getCurrentProfileVolumeInfo(VolumeManagerCommon.VolumeType.DRIVE)
.fileSystem) .fileSystem)
.populate(['/root/', '/team_drives/']); .populate(['/root/', '/team_drives/', '/Computers/']);
/** /**
* Suppress compiler warning for overwriting chrome.fileManagerPrivate. * Suppress compiler warning for overwriting chrome.fileManagerPrivate.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment