Commit 37fa72b0 authored by Joel Hockey's avatar Joel Hockey Committed by Commit Bot

CrOS FilesApp integrate with crostini

* New fileManagerPrivate APIs:
 - isCrostiniEnabled: returns true if device supports crostini
   and termina/penguin container exists.  This function is
   controlled by flag 'crostini-files'.
 - mountCrostiniContainer: starts and mounts container.

* Created new root type NavigationModelSFTPMountItem which uses
  a FakeEntry, similar to 'Recent', to show the 'Linux Files' root.

* Click handler for Linux Files will start and mount crostini container.

* Code in NavigationModel to order roots detects when mounted
  volumes already contains an FSP-provided mount for
  fileSystemId='crostini'.  In this case, the fake root is left out
  and the real volume should be in the same position.

* Error handling code for SFTP mount/disconnect to be implemented
  when sshfs is available.

Bug: 834103
Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: I8a3bec948871dc9671ea513d7378035d89d565eb
Reviewed-on: https://chromium-review.googlesource.com/1025545
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Reviewed-by: default avatarSasha Morrissey <sashab@chromium.org>
Reviewed-by: default avatarNaoki Fukino <fukino@chromium.org>
Reviewed-by: default avatarMark Pearson <mpearson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#557104}
parent 8be3b1a3
...@@ -215,6 +215,9 @@ ...@@ -215,6 +215,9 @@
<message name="IDS_FILE_BROWSER_DOWNLOADS_DIRECTORY_LABEL" desc="Downloads local directory label."> <message name="IDS_FILE_BROWSER_DOWNLOADS_DIRECTORY_LABEL" desc="Downloads local directory label.">
Downloads Downloads
</message> </message>
<message name="IDS_FILE_BROWSER_LINUX_FILES_ROOT_LABEL" desc="A label for the 'Linux Files' root which shows crostini files.">
Linux Files
</message>
<message name="IDS_FILE_BROWSER_MEDIA_VIEW_IMAGES_ROOT_LABEL" desc="A label for the 'Images' root of media views."> <message name="IDS_FILE_BROWSER_MEDIA_VIEW_IMAGES_ROOT_LABEL" desc="A label for the 'Images' root of media views.">
Images Images
</message> </message>
......
...@@ -11,11 +11,13 @@ ...@@ -11,11 +11,13 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/command_line.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/drive/file_system_util.h" #include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h" #include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h"
#include "chrome/browser/chromeos/file_manager/fileapi_util.h" #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
...@@ -41,6 +43,7 @@ ...@@ -41,6 +43,7 @@
#include "chrome/common/extensions/api/manifest_types.h" #include "chrome/common/extensions/api/manifest_types.h"
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "chrome/services/file_util/public/cpp/zip_file_creator.h" #include "chrome/services/file_util/public/cpp/zip_file_creator.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/settings/timezone_settings.h" #include "chromeos/settings/timezone_settings.h"
#include "components/account_id/account_id.h" #include "components/account_id/account_id.h"
#include "components/drive/drive_pref_names.h" #include "components/drive/drive_pref_names.h"
...@@ -640,6 +643,25 @@ void FileManagerPrivateConfigureVolumeFunction::OnCompleted( ...@@ -640,6 +643,25 @@ void FileManagerPrivateConfigureVolumeFunction::OnCompleted(
Respond(NoArguments()); Respond(NoArguments());
} }
bool IsCrostiniEnabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kCrostiniFiles) &&
crostini::CrostiniManager::IsCrosTerminaInstalled();
}
ExtensionFunction::ResponseAction
FileManagerPrivateIsCrostiniEnabledFunction::Run() {
return RespondNow(
OneArgument(std::make_unique<base::Value>(IsCrostiniEnabled())));
}
ExtensionFunction::ResponseAction
FileManagerPrivateMountCrostiniContainerFunction::Run() {
// TOOD(https://crbug.com/832509): implement MountCrostiniContainer.
DCHECK(IsCrostiniEnabled());
return RespondNow(NoArguments());
}
FileManagerPrivateInternalGetCustomActionsFunction:: FileManagerPrivateInternalGetCustomActionsFunction::
FileManagerPrivateInternalGetCustomActionsFunction() FileManagerPrivateInternalGetCustomActionsFunction()
: chrome_details_(this) {} : chrome_details_(this) {}
......
...@@ -261,6 +261,34 @@ class FileManagerPrivateConfigureVolumeFunction ...@@ -261,6 +261,34 @@ class FileManagerPrivateConfigureVolumeFunction
DISALLOW_COPY_AND_ASSIGN(FileManagerPrivateConfigureVolumeFunction); DISALLOW_COPY_AND_ASSIGN(FileManagerPrivateConfigureVolumeFunction);
}; };
// Implements the chrome.fileManagerPrivate.isCrostiniEnabled method.
// Gets crostini sftp mount params.
class FileManagerPrivateIsCrostiniEnabledFunction
: public UIThreadExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("fileManagerPrivate.isCrostiniEnabled",
FILEMANAGERPRIVATE_ISCROSTINIENABLED)
protected:
~FileManagerPrivateIsCrostiniEnabledFunction() override {}
ResponseAction Run() override;
};
// Implements the chrome.fileManagerPrivate.mountCrostiniContainer method.
// Starts and mounts crostini container.
class FileManagerPrivateMountCrostiniContainerFunction
: public UIThreadExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("fileManagerPrivate.mountCrostiniContainer",
FILEMANAGERPRIVATE_MOUNTCROSTINICONTAINER)
protected:
~FileManagerPrivateMountCrostiniContainerFunction() override {}
ResponseAction Run() override;
};
// Implements the chrome.fileManagerPrivate.getCustomActions method. // Implements the chrome.fileManagerPrivate.getCustomActions method.
class FileManagerPrivateInternalGetCustomActionsFunction class FileManagerPrivateInternalGetCustomActionsFunction
: public UIThreadExtensionFunction { : public UIThreadExtensionFunction {
......
...@@ -724,6 +724,7 @@ ExtensionFunction::ResponseAction FileManagerPrivateGetStringsFunction::Run() { ...@@ -724,6 +724,7 @@ ExtensionFunction::ResponseAction FileManagerPrivateGetStringsFunction::Run() {
SET_STRING("TOTAL_FILE_COUNT", IDS_FILE_BROWSER_TOTAL_FILE_COUNT_LABEL); SET_STRING("TOTAL_FILE_COUNT", IDS_FILE_BROWSER_TOTAL_FILE_COUNT_LABEL);
SET_STRING("IMAGE_RESOLUTION_COLUMN_LABEL", SET_STRING("IMAGE_RESOLUTION_COLUMN_LABEL",
IDS_FILE_BROWSER_IMAGE_RESOLUTION_COLUMN_LABEL); IDS_FILE_BROWSER_IMAGE_RESOLUTION_COLUMN_LABEL);
SET_STRING("LINUX_FILES_ROOT_LABEL", IDS_FILE_BROWSER_LINUX_FILES_ROOT_LABEL);
SET_STRING("MEDIA_ARTIST_COLUMN_LABEL", SET_STRING("MEDIA_ARTIST_COLUMN_LABEL",
IDS_FILE_BROWSER_MEDIA_ARTIST_COLUMN_LABEL); IDS_FILE_BROWSER_MEDIA_ARTIST_COLUMN_LABEL);
SET_STRING("MEDIA_TITLE_COLUMN_LABEL", SET_STRING("MEDIA_TITLE_COLUMN_LABEL",
......
...@@ -1072,6 +1072,13 @@ interface Functions { ...@@ -1072,6 +1072,13 @@ interface Functions {
[nocompile] [nocompile]
static void getRecentFiles(SourceRestriction restriction, static void getRecentFiles(SourceRestriction restriction,
GetRecentFilesCallback callback); GetRecentFilesCallback callback);
// Returns true if crostini is enabled.
// |callback|
static void isCrostiniEnabled(BooleanCallback callback);
// Starts and mounts crostini container.
static void mountCrostiniContainer();
}; };
interface Events { interface Events {
......
...@@ -1310,6 +1310,8 @@ enum HistogramValue { ...@@ -1310,6 +1310,8 @@ enum HistogramValue {
WALLPAPERPRIVATE_GETCURRENTWALLPAPERTHUMBNAIL, WALLPAPERPRIVATE_GETCURRENTWALLPAPERTHUMBNAIL,
ACCESSIBILITY_PRIVATE_ONSELECTTOSPEAKSTATECHANGED, ACCESSIBILITY_PRIVATE_ONSELECTTOSPEAKSTATECHANGED,
INPUTMETHODPRIVATE_GETCOMPOSITIONBOUNDS, INPUTMETHODPRIVATE_GETCOMPOSITIONBOUNDS,
FILEMANAGERPRIVATE_ISCROSTINIENABLED,
FILEMANAGERPRIVATE_MOUNTCROSTINICONTAINER,
// Last entry: Add new entries above, then run: // Last entry: Add new entries above, then run:
// python tools/metrics/histograms/update_extension_histograms.py // python tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY ENUM_BOUNDARY
......
...@@ -728,6 +728,17 @@ chrome.fileManagerPrivate.getRecentFiles = function(restriction, callback) {}; ...@@ -728,6 +728,17 @@ chrome.fileManagerPrivate.getRecentFiles = function(restriction, callback) {};
chrome.fileManagerPrivate.executeCustomAction = function( chrome.fileManagerPrivate.executeCustomAction = function(
entries, actionId, callback) {}; entries, actionId, callback) {};
/**
* Returns true if crostini is enabled.
* @param {function(!boolean)} callback
*/
chrome.fileManagerPrivate.isCrostiniEnabled = function(callback) {};
/**
* Starts and mounts crostini container.
*/
chrome.fileManagerPrivate.mountCrostiniContainer = function() {};
/** @type {!ChromeEvent} */ /** @type {!ChromeEvent} */
chrome.fileManagerPrivate.onMountCompleted; chrome.fileManagerPrivate.onMountCompleted;
......
...@@ -15400,6 +15400,8 @@ Called by update_net_error_codes.py.--> ...@@ -15400,6 +15400,8 @@ Called by update_net_error_codes.py.-->
<int value="1247" label="WALLPAPERPRIVATE_GETCURRENTWALLPAPERTHUMBNAIL"/> <int value="1247" label="WALLPAPERPRIVATE_GETCURRENTWALLPAPERTHUMBNAIL"/>
<int value="1248" label="ACCESSIBILITY_PRIVATE_ONSELECTTOSPEAKSTATECHANGED"/> <int value="1248" label="ACCESSIBILITY_PRIVATE_ONSELECTTOSPEAKSTATECHANGED"/>
<int value="1249" label="INPUTMETHODPRIVATE_GETCOMPOSITIONBOUNDS"/> <int value="1249" label="INPUTMETHODPRIVATE_GETCOMPOSITIONBOUNDS"/>
<int value="1250" label="FILEMANAGERPRIVATE_ISCROSTINIENABLED"/>
<int value="1251" label="FILEMANAGERPRIVATE_MOUNTCROSTINICONTAINER"/>
</enum> </enum>
<enum name="ExtensionIconState"> <enum name="ExtensionIconState">
...@@ -492,20 +492,26 @@ FileBrowserBackgroundImpl.prototype.onMountCompletedInternal_ = function( ...@@ -492,20 +492,26 @@ FileBrowserBackgroundImpl.prototype.onMountCompletedInternal_ = function(
event) { event) {
// If there is no focused window, then create a new one opened on the // If there is no focused window, then create a new one opened on the
// mounted FSP volume. // mounted FSP volume.
this.findFocusedWindow_().then(function(key) { this.findFocusedWindow_()
if (key === null && .then(function(key) {
event.eventType === 'mount' && let statusOK = event.status === 'success' ||
(event.status === 'success' || event.status === 'error_path_already_mounted';
event.status === 'error_path_already_mounted') && let volumeTypeOK =
event.volumeMetadata.mountContext === 'user' && event.volumeMetadata.source === VolumeManagerCommon.Source.FILE ||
event.volumeMetadata.volumeType === VolumeManagerCommon.getProvidedFileSystemIdFromVolumeId(
VolumeManagerCommon.VolumeType.PROVIDED && event.volumeId) ===
event.volumeMetadata.source === VolumeManagerCommon.Source.FILE) { VolumeManagerCommon.ProvidedFileSystem.CROSTINI;
this.navigateToVolumeWhenReady_(event.volumeMetadata.volumeId); if (key === null && event.eventType === 'mount' && statusOK &&
} event.volumeMetadata.mountContext === 'user' &&
}.bind(this)).catch(function(error) { event.volumeMetadata.volumeType ===
console.error(error.stack || error); VolumeManagerCommon.VolumeType.PROVIDED &&
}); volumeTypeOK) {
this.navigateToVolumeWhenReady_(event.volumeMetadata.volumeId);
}
}.bind(this))
.catch(function(error) {
console.error(error.stack || error);
});
}; };
/** /**
......
...@@ -102,6 +102,9 @@ VolumeManagerCommon.RootType = { ...@@ -102,6 +102,9 @@ VolumeManagerCommon.RootType = {
// 'Add new services' menu item. // 'Add new services' menu item.
ADD_NEW_SERVICES_MENU: 'add_new_services_menu', ADD_NEW_SERVICES_MENU: 'add_new_services_menu',
// Fake root for SFTP Mount such as Linux Files.
SFTP_MOUNT: 'sftp_mount',
}; };
Object.freeze(VolumeManagerCommon.RootType); Object.freeze(VolumeManagerCommon.RootType);
...@@ -131,6 +134,7 @@ VolumeManagerCommon.RootTypesForUMA = [ ...@@ -131,6 +134,7 @@ VolumeManagerCommon.RootTypesForUMA = [
VolumeManagerCommon.RootType.RECENT, VolumeManagerCommon.RootType.RECENT,
VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT, VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT,
VolumeManagerCommon.RootType.ADD_NEW_SERVICES_MENU, VolumeManagerCommon.RootType.ADD_NEW_SERVICES_MENU,
VolumeManagerCommon.RootType.SFTP_MOUNT,
]; ];
console.assert( console.assert(
Object.keys(VolumeManagerCommon.RootType).length === Object.keys(VolumeManagerCommon.RootType).length ===
...@@ -321,6 +325,26 @@ VolumeManagerCommon.getMediaViewRootTypeFromVolumeId = function(volumeId) { ...@@ -321,6 +325,26 @@ VolumeManagerCommon.getMediaViewRootTypeFromVolumeId = function(volumeId) {
volumeId.split(':', 2)[1]); volumeId.split(':', 2)[1]);
}; };
/**
* List of known FSP-provided fileSystemId values.
*
* @enum {string}
* @const
*/
VolumeManagerCommon.ProvidedFileSystem = {
CROSTINI: 'crostini',
};
/**
* Obtains fileSystemId from volumeId of FSP-provided mount.
* @param {string} volumeId Volume ID.
* @return {string|undefined}
*/
VolumeManagerCommon.getProvidedFileSystemIdFromVolumeId = function(volumeId) {
return volumeId ? volumeId.split(':', 3)[2] : undefined;
};
/** /**
* Fake entries for virtual folders which hold Google Drive offline files, * Fake entries for virtual folders which hold Google Drive offline files,
* Google Drive "Shared with me" files, and mixed Recent files. * Google Drive "Shared with me" files, and mixed Recent files.
......
...@@ -234,7 +234,8 @@ tree .tree-item[selected] > .tree-row > .shared[file-type-icon='folder'] { ...@@ -234,7 +234,8 @@ tree .tree-item[selected] > .tree-row > .shared[file-type-icon='folder'] {
url(../images/volumes/2x/service_drive_active.png) 2x); url(../images/volumes/2x/service_drive_active.png) 2x);
} }
[volume-type-icon='team_drives_grand_root'],[volume-type-icon='team_drive'] { [volume-type-icon='team_drives_grand_root'],
[volume-type-icon='team_drive'] {
background-image: -webkit-image-set( background-image: -webkit-image-set(
url(../images/volumes/team_drive.png) 1x, url(../images/volumes/team_drive.png) 1x,
url(../images/volumes/2x/team_drive.png) 2x); url(../images/volumes/2x/team_drive.png) 2x);
...@@ -416,3 +417,20 @@ cr-menu-item[command='#install-new-extension'] .icon.start { ...@@ -416,3 +417,20 @@ cr-menu-item[command='#install-new-extension'] .icon.start {
url(../images/volumes/recent_active.png) 1x, url(../images/volumes/recent_active.png) 1x,
url(../images/volumes/2x/recent_active.png) 2x); url(../images/volumes/2x/recent_active.png) 2x);
} }
[sftp-mount-icon='linux-files'],
[volume-subtype='crostini'] {
/* Need !important to override inline style applied to provided volumes. */
background-image: -webkit-image-set(
url(../images/volumes/linux_files.png) 1x,
url(../images/volumes/2x/linux_files.png) 2x) !important;
}
.tree-row[selected] [sftp-mount-icon='linux-files'],
.tree-row[selected] [volume-subtype='crostini'] {
/* Need !important to override inline style applied to provided volumes. */
background-image: -webkit-image-set(
url(../images/volumes/linux_files_active.png) 1x,
url(../images/volumes/2x/linux_files_active.png) 2x) !important;
}
...@@ -1164,13 +1164,15 @@ DirectoryModel.prototype.onVolumeInfoListUpdated_ = function(event) { ...@@ -1164,13 +1164,15 @@ DirectoryModel.prototype.onVolumeInfoListUpdated_ = function(event) {
}.bind(this)); }.bind(this));
} }
// If a new file backed provided volume is mounted, then redirect to it in // If a new file backed provided volume, or crostini is mounted,
// the focused window. Note, that this is a temporary solution for // then redirect to it in the focused window.
// crbug.com/427776. // Note, that this is a temporary solution for https://crbug.com/427776.
if (window.isFocused() && if (window.isFocused() && event.added.length === 1 &&
event.added.length === 1 &&
event.added[0].volumeType === VolumeManagerCommon.VolumeType.PROVIDED && event.added[0].volumeType === VolumeManagerCommon.VolumeType.PROVIDED &&
event.added[0].source === VolumeManagerCommon.Source.FILE) { (event.added[0].source === VolumeManagerCommon.Source.FILE ||
VolumeManagerCommon.getProvidedFileSystemIdFromVolumeId(
event.added[0].volumeId) ===
VolumeManagerCommon.ProvidedFileSystem.CROSTINI)) {
event.added[0].resolveDisplayRoot().then(function(displayRoot) { event.added[0].resolveDisplayRoot().then(function(displayRoot) {
// Resolving a display root on FSP volumes is instant, despite the // Resolving a display root on FSP volumes is instant, despite the
// asynchronous call. // asynchronous call.
......
...@@ -10,6 +10,7 @@ var NavigationModelItemType = { ...@@ -10,6 +10,7 @@ var NavigationModelItemType = {
VOLUME: 'volume', VOLUME: 'volume',
MENU: 'menu', MENU: 'menu',
RECENT: 'recent', RECENT: 'recent',
SFTP_MOUNT: 'sftp_mount',
}; };
/** /**
...@@ -130,6 +131,38 @@ NavigationModelRecentItem.prototype = /** @struct */ { ...@@ -130,6 +131,38 @@ NavigationModelRecentItem.prototype = /** @struct */ {
} }
}; };
/**
* Item of NavigationListModel for an SFTP Mount as used by Linux files.
*
* @param {string} label Label on the item.
* @param {!FakeEntry} entry Fake entry for the SFTP Mount root folder.
* @param {string} icon CSS icon.
* @constructor
* @extends {NavigationModelItem}
* @struct
*/
function NavigationModelSFTPMountItem(label, entry, icon) {
NavigationModelItem.call(this, label, NavigationModelItemType.SFTP_MOUNT);
this.entry_ = entry;
this.icon_ = icon;
}
NavigationModelSFTPMountItem.prototype = /** @struct */ {
__proto__: NavigationModelItem.prototype,
get entry() {
return this.entry_;
},
get icon() {
return this.icon_;
},
/**
* Start crostini container and mount it.
*/
mount: function() {
chrome.fileManagerPrivate.mountCrostiniContainer();
},
};
/** /**
* A navigation list model. This model combines multiple models. * A navigation list model. This model combines multiple models.
* @param {!VolumeManagerWrapper} volumeManager VolumeManagerWrapper instance. * @param {!VolumeManagerWrapper} volumeManager VolumeManagerWrapper instance.
...@@ -162,13 +195,20 @@ function NavigationListModel( ...@@ -162,13 +195,20 @@ function NavigationListModel(
*/ */
this.recentModelItem_ = recentModelItem; this.recentModelItem_ = recentModelItem;
/**
* Root folder for crostini Linux Files.
* This field will be set asynchronously after calling
* chrome.fileManagerPrivate.isCrostiniEnabled.
* @private {NavigationModelSFTPMountItem}
*/
this.linuxFilesItem_ = null;
/** /**
* @private {NavigationModelMenuItem} * @private {NavigationModelMenuItem}
* @const * @const
*/ */
this.addNewServicesItem_ = addNewServicesItem; this.addNewServicesItem_ = addNewServicesItem;
/** /**
* All root navigation items in display order. * All root navigation items in display order.
* @private {!Array<!NavigationModelItem>} * @private {!Array<!NavigationModelItem>}
...@@ -210,7 +250,25 @@ function NavigationListModel( ...@@ -210,7 +250,25 @@ function NavigationListModel(
this.shortcutList_.push(entryToModelItem(shortcutEntry)); this.shortcutList_.push(entryToModelItem(shortcutEntry));
} }
// Reorder volumes, shortcuts, and optional items. // Check if crostini is enabled to create linuxFilesItem_.
chrome.fileManagerPrivate.isCrostiniEnabled((enabled) => {
if (!enabled)
return;
this.linuxFilesItem_ = new NavigationModelSFTPMountItem(
str('LINUX_FILES_ROOT_LABEL'), {
isDirectory: true,
rootType: VolumeManagerCommon.RootType.SFTP_MOUNT,
toURL: function() {
return 'fake-entry://linux-files';
},
},
'linux-files');
// Reorder items to ensure Linux Files is shown.
this.reorderNavigationItems_();
});
// Reorder volumes, shortcuts, and optional items for initial display.
this.reorderNavigationItems_(); this.reorderNavigationItems_();
// Generates a combined 'permuted' event from an event of either volumeList or // Generates a combined 'permuted' event from an event of either volumeList or
...@@ -333,14 +391,30 @@ NavigationListModel.prototype = { ...@@ -333,14 +391,30 @@ NavigationListModel.prototype = {
* 1. Volumes. * 1. Volumes.
* 2. If Downloads exists, then immediately after Downloads should be: * 2. If Downloads exists, then immediately after Downloads should be:
* 2a. Recent if it exists. * 2a. Recent if it exists.
* 2b. Linux Files if it exists and is not mounted.
* When mounted, it will be located in Volumes at this position.
* 3. Shortcuts. * 3. Shortcuts.
* 4. Add new services if it exists. * 4. Add new services if it exists.
* @private * @private
*/ */
NavigationListModel.prototype.reorderNavigationItems_ = function() { NavigationListModel.prototype.reorderNavigationItems_ = function() {
// Check if Linux files already mounted.
let linuxFilesMounted = false;
for (let i = 0; i < this.volumeList_.length; i++) {
if (VolumeManagerCommon.getProvidedFileSystemIdFromVolumeId(
this.volumeList_[i].volumeInfo.volumeId) ===
VolumeManagerCommon.ProvidedFileSystem.CROSTINI) {
linuxFilesMounted = true;
break;
}
}
// Items as per required order. // Items as per required order.
this.navigationItems_ = this.volumeList_.slice(); this.navigationItems_ = this.volumeList_.slice();
var downloadsVolumeIndex = this.findDownloadsVolumeIndex_(); var downloadsVolumeIndex = this.findDownloadsVolumeIndex_();
if (this.linuxFilesItem_ && !linuxFilesMounted && downloadsVolumeIndex >= 0)
this.navigationItems_.splice(
downloadsVolumeIndex + 1, 0, this.linuxFilesItem_);
if (this.recentModelItem_ && downloadsVolumeIndex >= 0) if (this.recentModelItem_ && downloadsVolumeIndex >= 0)
this.navigationItems_.splice( this.navigationItems_.splice(
downloadsVolumeIndex + 1, 0, this.recentModelItem_); downloadsVolumeIndex + 1, 0, this.recentModelItem_);
......
...@@ -11,7 +11,8 @@ var hoge; ...@@ -11,7 +11,8 @@ var hoge;
// Set up string assets. // Set up string assets.
loadTimeData.data = { loadTimeData.data = {
DRIVE_DIRECTORY_LABEL: 'My Drive', DRIVE_DIRECTORY_LABEL: 'My Drive',
DOWNLOADS_DIRECTORY_LABEL: 'Downloads' DOWNLOADS_DIRECTORY_LABEL: 'Downloads',
LINUX_FILES_ROOT_LABEL: 'Linux Files',
}; };
function setUp() { function setUp() {
...@@ -19,6 +20,15 @@ function setUp() { ...@@ -19,6 +20,15 @@ function setUp() {
// Override VolumeInfo.prototype.resolveDisplayRoot. // Override VolumeInfo.prototype.resolveDisplayRoot.
VolumeInfoImpl.prototype.resolveDisplayRoot = function() {}; VolumeInfoImpl.prototype.resolveDisplayRoot = function() {};
// Mock chrome.fileManagerPrivate.isCrostiniEnabled.
// TODO(crbug.com/834103): Add integration test for Crostini.
chrome.fileManagerPrivate = {
crostiniEnabled: false,
isCrostiniEnabled: function(callback) {
callback(this.crostiniEnabled);
},
};
drive = new MockFileSystem('drive'); drive = new MockFileSystem('drive');
hoge = new MockFileSystem('removable:hoge'); hoge = new MockFileSystem('removable:hoge');
} }
...@@ -33,22 +43,24 @@ function testModel() { ...@@ -33,22 +43,24 @@ function testModel() {
} }
}; };
var recentItem = new NavigationModelRecentItem('recent-label', fakeEntry); var recentItem = new NavigationModelRecentItem('recent-label', fakeEntry);
chrome.fileManagerPrivate.crostiniEnabled = true;
var addNewServicesItem = new NavigationModelMenuItem( var addNewServicesItem = new NavigationModelMenuItem(
'menu-button-label', '#add-new-services', 'menu-button-icon'); 'menu-button-label', '#add-new-services', 'menu-button-icon');
var model = new NavigationListModel( var model = new NavigationListModel(
volumeManager, shortcutListModel, recentItem, addNewServicesItem); volumeManager, shortcutListModel, recentItem, addNewServicesItem);
assertEquals(5, model.length); assertEquals(6, model.length);
assertEquals('drive', model.item(0).volumeInfo.volumeId); assertEquals('drive', model.item(0).volumeInfo.volumeId);
assertEquals('downloads', model.item(1).volumeInfo.volumeId); assertEquals('downloads', model.item(1).volumeInfo.volumeId);
assertEquals('fake-entry://recent', model.item(2).entry.toURL()); assertEquals('fake-entry://recent', model.item(2).entry.toURL());
assertEquals('/root/shortcut', model.item(3).entry.fullPath); assertEquals('fake-entry://linux-files', model.item(3).entry.toURL());
assertEquals('menu-button-label', model.item(4).label); assertEquals('/root/shortcut', model.item(4).entry.fullPath);
assertEquals('#add-new-services', model.item(4).menu); assertEquals('menu-button-label', model.item(5).label);
assertEquals('menu-button-icon', model.item(4).icon); assertEquals('#add-new-services', model.item(5).menu);
assertEquals('menu-button-icon', model.item(5).icon);
} }
function testNoRecent() { function testNoRecentOrLinuxFiles() {
var volumeManager = new MockVolumeManagerWrapper(); var volumeManager = new MockVolumeManagerWrapper();
var shortcutListModel = new MockFolderShortcutDataModel( var shortcutListModel = new MockFolderShortcutDataModel(
[new MockFileEntry(drive, '/root/shortcut')]); [new MockFileEntry(drive, '/root/shortcut')]);
......
...@@ -700,6 +700,13 @@ VolumeItem.prototype.setupIcon_ = function(icon, volumeInfo) { ...@@ -700,6 +700,13 @@ VolumeItem.prototype.setupIcon_ = function(icon, volumeInfo) {
'volume-subtype', 'volume-subtype',
VolumeManagerCommon.getMediaViewRootTypeFromVolumeId( VolumeManagerCommon.getMediaViewRootTypeFromVolumeId(
volumeInfo.volumeId)); volumeInfo.volumeId));
} else if (
volumeInfo.volumeType === VolumeManagerCommon.VolumeType.PROVIDED) {
icon.setAttribute(
'volume-subtype',
VolumeManagerCommon.getProvidedFileSystemIdFromVolumeId(
volumeInfo.volumeId) ||
'');
} else { } else {
icon.setAttribute('volume-subtype', volumeInfo.deviceType || ''); icon.setAttribute('volume-subtype', volumeInfo.deviceType || '');
} }
...@@ -1184,6 +1191,54 @@ RecentItem.prototype.activate = function() { ...@@ -1184,6 +1191,54 @@ RecentItem.prototype.activate = function() {
this.parentTree_.directoryModel.activateDirectoryEntry(this.entry); this.parentTree_.directoryModel.activateDirectoryEntry(this.entry);
}; };
////////////////////////////////////////////////////////////////////////////////
// SFTPMountItem
/**
* A TreeItem which represents a directory to be mounted using SFTP.
*
* @param {!NavigationModelSFTPMountItem} modelItem
* @param {!DirectoryTree} tree Current tree, which contains this item.
* @extends {cr.ui.TreeItem}
* @constructor
*/
function SFTPMountItem(modelItem, tree) {
var item = new cr.ui.TreeItem();
item.__proto__ = SFTPMountItem.prototype;
item.parentTree_ = tree;
item.modelItem_ = modelItem;
item.innerHTML = TREE_ITEM_INNER_HTML;
item.label = modelItem.label;
var icon = queryRequiredElement('.icon', item);
icon.classList.add('item-icon');
icon.setAttribute('sftp-mount-icon', item.modelItem_.icon);
return item;
}
SFTPMountItem.prototype = {
__proto__: cr.ui.TreeItem.prototype,
get entry() {
return null;
},
get modelItem() {
return this.modelItem_;
},
get labelElement() {
return this.firstElementChild.querySelector('.label');
}
};
/**
* @override
*/
SFTPMountItem.prototype.handleClick = function(e) {
this.selected = true;
this.modelItem_.mount();
};
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// DirectoryTree // DirectoryTree
...@@ -1373,6 +1428,9 @@ DirectoryTree.prototype.updateSubElementsFromList = function(recursive) { ...@@ -1373,6 +1428,9 @@ DirectoryTree.prototype.updateSubElementsFromList = function(recursive) {
case NavigationModelItemType.RECENT: case NavigationModelItemType.RECENT:
this.addAt(new RecentItem(modelItem, this), itemIndex); this.addAt(new RecentItem(modelItem, this), itemIndex);
break; break;
case NavigationModelItemType.SFTP_MOUNT:
this.addAt(new SFTPMountItem(modelItem, this), itemIndex);
break;
} }
} }
itemIndex++; itemIndex++;
......
...@@ -53,6 +53,7 @@ loadTimeData.data = new Proxy( ...@@ -53,6 +53,7 @@ loadTimeData.data = new Proxy(
'utm_campaign=gsg', 'utm_campaign=gsg',
IMAGE_FILE_TYPE: '$1 image', IMAGE_FILE_TYPE: '$1 image',
INSTALL_NEW_EXTENSION_LABEL: 'Install new from the webstore', INSTALL_NEW_EXTENSION_LABEL: 'Install new from the webstore',
LINUX_FILES_ROOT_LABEL: 'Linux Files',
MANY_ENTRIES_SELECTED: '$1 items selected', MANY_ENTRIES_SELECTED: '$1 items selected',
MANY_FILES_SELECTED: '$1 files selected', MANY_FILES_SELECTED: '$1 files selected',
METADATA_BOX_ALBUM_TITLE: 'Album', METADATA_BOX_ALBUM_TITLE: 'Album',
...@@ -68,6 +69,8 @@ loadTimeData.data = new Proxy( ...@@ -68,6 +69,8 @@ loadTimeData.data = new Proxy(
OPEN_LABEL: 'Open', OPEN_LABEL: 'Open',
PLAIN_TEXT_FILE_TYPE: 'Plain text', PLAIN_TEXT_FILE_TYPE: 'Plain text',
PREPARING_LABEL: 'Preparing', PREPARING_LABEL: 'Preparing',
SEE_MENU_FOR_ACTIONS: 'More options available on the action bar. ' +
'Press Alt + A to focus the action bar.',
SIZE_COLUMN_LABEL: 'Size', SIZE_COLUMN_LABEL: 'Size',
SPACE_AVAILABLE: '$1 available', SPACE_AVAILABLE: '$1 available',
STATUS_COLUMN_LABEL: 'Status', STATUS_COLUMN_LABEL: 'Status',
......
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