Skip to content

Commit 270e724

Browse files
authored
Merge pull request #89 from DenisaCG/excludeAndIncludeDrives
Add support for including and excluding drives from listing
2 parents 8089a33 + 93effd6 commit 270e724

File tree

13 files changed

+590
-308
lines changed

13 files changed

+590
-308
lines changed

jupyter_drives/handlers.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,20 @@ class ConfigJupyterDrivesHandler(JupyterDrivesAPIHandler):
4949
def initialize(self, logger: logging.Logger, manager: JupyterDrivesManager):
5050
return super().initialize(logger, manager)
5151

52+
@tornado.web.authenticated
53+
async def get(self):
54+
result = self._manager.get_excluded_drives()
55+
self.finish(json.dumps(result["data"]))
56+
5257
@tornado.web.authenticated
5358
async def post(self):
5459
body = self.get_json_body()
55-
result = self._manager.set_listing_limit(**body)
60+
if 'new_limit' in body:
61+
result = self._manager.set_listing_limit(**body)
62+
if 'exclude_drive_name' in body:
63+
result = self._manager.exclude_drive(**body)
64+
if 'include_drive_name' in body:
65+
result = self._manager.include_drive(**body)
5666
self.finish(result)
5767

5868
class ListJupyterDrivesHandler(JupyterDrivesAPIHandler):

jupyter_drives/manager.py

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(self, config: traitlets.config.Config) -> None:
5353
self._max_files_listed = 1025
5454
self._drives = None
5555
self._external_drives = {}
56+
self._excluded_drives = set()
5657

5758
# instate fsspec file system
5859
self._file_system = fsspec.filesystem(self._config.provider, asynchronous=True)
@@ -171,6 +172,65 @@ def set_listing_limit(self, new_limit):
171172

172173
return
173174

175+
def exclude_drive(self, exclude_drive_name):
176+
"""Exclude drive from listing.
177+
178+
Args:
179+
exclude_bucket_name: drive to exclude
180+
"""
181+
try:
182+
self._excluded_drives.add(exclude_drive_name);
183+
except Exception as e:
184+
raise tornado.web.HTTPError(
185+
status_code= httpx.codes.BAD_REQUEST,
186+
reason= f"The following error occured when excluding the drive: {e}"
187+
)
188+
189+
return
190+
191+
def get_excluded_drives(self):
192+
"""Get list of excluded drives.
193+
194+
Returns:
195+
List of excluded drives and their properties.
196+
"""
197+
data = []
198+
for drive in self._excluded_drives:
199+
try:
200+
data.append({
201+
"name": drive,
202+
"region": self._config.region_name if drive not in self._content_managers else self._content_managers[drive]["location"],
203+
"creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'),
204+
"mounted": False if drive not in self._content_managers else True,
205+
"provider": self._config.provider
206+
})
207+
except Exception as e:
208+
raise tornado.web.HTTPError(
209+
status_code=httpx.codes.BAD_REQUEST,
210+
reason=f"The following error occurred when listing excluded drives: {e}",
211+
)
212+
213+
response = {
214+
"data": data
215+
}
216+
return response
217+
218+
def include_drive(self, include_drive_name):
219+
"""Include drive in listing.
220+
221+
Args:
222+
include_bucket_name: drive to include in listing
223+
"""
224+
try:
225+
self._excluded_drives.remove(include_drive_name);
226+
except Exception as e:
227+
raise tornado.web.HTTPError(
228+
status_code= httpx.codes.BAD_REQUEST,
229+
reason= f"The following error occurred when including the drive: {e}"
230+
)
231+
232+
return
233+
174234
async def list_drives(self):
175235
"""Get list of available drives.
176236
@@ -196,26 +256,28 @@ async def list_drives(self):
196256
)
197257

198258
for result in results:
199-
data.append(
200-
{
201-
"name": result.name,
202-
"region": self._config.region_name,
203-
"creationDate": result.extra["creation_date"],
204-
"mounted": False if result.name not in self._content_managers else True,
205-
"provider": self._config.provider
206-
}
207-
)
259+
if result.name not in self._excluded_drives:
260+
data.append(
261+
{
262+
"name": result.name,
263+
"region": self._config.region_name if result.name not in self._content_managers else self._content_managers[result.name]["location"],
264+
"creationDate": result.extra["creation_date"],
265+
"mounted": False if result.name not in self._content_managers else True,
266+
"provider": self._config.provider
267+
}
268+
)
208269

209270
if len(self._external_drives) != 0:
210271
for drive in self._external_drives.values():
211272
try:
212-
data.append({
213-
"name": drive['url'],
214-
"region": self._config.region_name,
215-
"creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'),
216-
"mounted": False if result.name not in self._content_managers else True,
217-
"provider": self._config.provider
218-
})
273+
if drive['url'] not in self._excluded_drives:
274+
data.append({
275+
"name": drive['url'],
276+
"region": self._config.region_name if drive['url'] not in self._content_managers else self._content_managers[drive['url']]["location"],
277+
"creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'),
278+
"mounted": False if result.name not in self._content_managers else True,
279+
"provider": self._config.provider
280+
})
219281
except Exception as e:
220282
raise tornado.web.HTTPError(
221283
status_code=httpx.codes.BAD_REQUEST,

schema/drives-file-browser.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
"rank": 40
2929
},
3030
{
31-
"name": "new-drive",
32-
"command": "drives:create-new-drive",
31+
"name": "drives-manager",
32+
"command": "drives:open-drives-dialog",
3333
"label": "",
3434
"rank": 5
3535
}

src/contents.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import {
2222
copyObjects,
2323
presignedLink,
2424
createDrive,
25-
getDrivesList
25+
getDrivesList,
26+
excludeDrive,
27+
includeDrive
2628
} from './requests';
2729

2830
let data: Contents.IModel = {
@@ -683,6 +685,46 @@ export class Drive implements Contents.IDrive {
683685
return data;
684686
}
685687

688+
/**
689+
* Exclude drive from browser.
690+
*
691+
* @param driveName: The name of drive to exclude.
692+
*
693+
* @returns A promise which resolves with the contents model.
694+
*/
695+
async excludeDrive(driveName: string): Promise<Contents.IModel> {
696+
data = await excludeDrive(driveName);
697+
698+
Contents.validateContentsModel(data);
699+
this._fileChanged.emit({
700+
type: 'delete',
701+
oldValue: data,
702+
newValue: null
703+
});
704+
705+
return data;
706+
}
707+
708+
/**
709+
* Include drive in browser listing.
710+
*
711+
* @param driveName: The name of drive to include.
712+
*
713+
* @returns A promise which resolves with the contents model.
714+
*/
715+
async includeDrive(driveName: string): Promise<Contents.IModel> {
716+
data = await includeDrive(driveName);
717+
718+
Contents.validateContentsModel(data);
719+
this._fileChanged.emit({
720+
type: 'new',
721+
oldValue: null,
722+
newValue: data
723+
});
724+
725+
return data;
726+
}
727+
686728
/**
687729
* Create a checkpoint for a file.
688730
*

src/icons.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
import { LabIcon } from '@jupyterlab/ui-components';
22
import driveBrowserSvg from '../style/driveIconFileBrowser.svg';
3+
import addIconSvg from '../style/addIcon.svg';
4+
import removeIconSvg from '../style/removeIcon.svg';
35

46
export const driveBrowserIcon = new LabIcon({
57
name: 'jupyter-drives:drive-browser',
68
svgstr: driveBrowserSvg
79
});
10+
11+
export const addIcon = new LabIcon({
12+
name: 'jupyter-drives:add-drive',
13+
svgstr: addIconSvg
14+
});
15+
16+
export const removeIcon = new LabIcon({
17+
name: 'jupyter-drives:remove-drive',
18+
svgstr: removeIconSvg
19+
});

src/plugins/driveBrowserPlugin.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { PageConfig, PathExt } from '@jupyterlab/coreutils';
3333
import { CommandRegistry } from '@lumino/commands';
3434
import { Widget } from '@lumino/widgets';
3535

36-
import { driveBrowserIcon } from '../icons';
36+
import { driveBrowserIcon, removeIcon } from '../icons';
3737
import { Drive } from '../contents';
3838
import { setListingLimit } from '../requests';
3939
import { CommandIDs } from '../token';
@@ -423,7 +423,7 @@ namespace Private {
423423
});
424424

425425
app.commands.addCommand(CommandIDs.addPublicDrive, {
426-
isEnabled: () => {
426+
isVisible: () => {
427427
return browser.model.path === 's3:';
428428
},
429429
execute: async () => {
@@ -542,5 +542,33 @@ namespace Private {
542542
icon: fileIcon.bindprops({ stylesheet: 'menuItem' }),
543543
label: 'Copy Path'
544544
});
545+
546+
app.commands.addCommand(CommandIDs.excludeDrive, {
547+
isVisible: () => {
548+
return browser.model.path === 's3:';
549+
},
550+
execute: async () => {
551+
const widget = tracker.currentWidget;
552+
if (!widget) {
553+
return;
554+
}
555+
const item = widget.selectedItems().next();
556+
if (item.done) {
557+
return;
558+
}
559+
560+
const driveName: string = item.value.name;
561+
await drive.excludeDrive(driveName);
562+
},
563+
label: 'Remove Drive',
564+
icon: removeIcon.bindprops({ stylesheet: 'menuItem' })
565+
});
566+
567+
app.contextMenu.addItem({
568+
command: CommandIDs.excludeDrive,
569+
selector:
570+
'#drive-file-browser.jp-SidePanel .jp-DirListing-content .jp-DirListing-item[data-isdir]',
571+
rank: 110
572+
});
545573
}
546574
}

src/plugins/driveDialogPlugin.ts

Lines changed: 24 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import { ITranslator } from '@jupyterlab/translation';
77
import { addJupyterLabThemeChangeListener } from '@jupyter/web-components';
88
import { Dialog, showDialog } from '@jupyterlab/apputils';
99

10-
import { DriveListModel, DriveListView, IDrive } from './drivelistmanager';
10+
import { DriveListModel, DriveListView } from './drivelistmanager';
1111
import { driveBrowserIcon } from '../icons';
12-
import { CommandIDs } from '../token';
12+
import { CommandIDs, IDriveInfo } from '../token';
13+
import { getDrivesList, getExcludedDrives } from '../requests';
1314

1415
export const openDriveDialogPlugin: JupyterFrontEndPlugin<void> = {
1516
id: 'jupyter-drives:widget',
16-
description: 'Open a dialog to select drives to be added in the filebrowser.',
17+
description: 'Open a dialog to managed listed drives in the filebrowser.',
1718
requires: [IFileBrowserFactory, ITranslator],
1819
autoStart: true,
1920
activate: (
@@ -25,68 +26,29 @@ export const openDriveDialogPlugin: JupyterFrontEndPlugin<void> = {
2526
const { commands } = app;
2627
const { tracker } = factory;
2728
const trans = translator.load('jupyter_drives');
28-
const selectedDrivesModelMap = new Map<IDrive[], DriveListModel>();
29+
const selectedDrivesModelMap = new Map<
30+
Partial<IDriveInfo>[],
31+
DriveListModel
32+
>();
2933

30-
let selectedDrives: IDrive[] = [
31-
{
32-
name: 'CoconutDrive',
33-
url: '/coconut/url'
34-
}
35-
];
36-
37-
const availableDrives: IDrive[] = [
38-
{
39-
name: 'CoconutDrive',
40-
url: '/coconut/url'
41-
},
42-
{
43-
name: 'PearDrive',
44-
url: '/pear/url'
45-
},
46-
{
47-
name: 'StrawberryDrive',
48-
url: '/strawberrydrive/url'
49-
},
50-
{
51-
name: 'BlueberryDrive',
52-
url: '/blueberrydrive/url'
53-
},
54-
{
55-
name: '',
56-
url: '/mydrive/url'
57-
},
58-
{
59-
name: 'RaspberryDrive',
60-
url: '/raspberrydrive/url'
61-
},
34+
let selectedDrives: Partial<IDriveInfo>[] = [];
35+
getDrivesList().then((drives: IDriveInfo[]) => {
36+
selectedDrives = drives.map((drive: IDriveInfo) => ({
37+
name: drive.name,
38+
region: drive.region
39+
}));
40+
});
6241

63-
{
64-
name: 'PineAppleDrive',
65-
url: ''
66-
},
42+
let availableDrives: Partial<IDriveInfo>[] = [];
43+
getExcludedDrives().then((drives: IDriveInfo[]) => {
44+
availableDrives = drives.map((drive: IDriveInfo) => ({
45+
name: drive.name,
46+
region: drive.region
47+
}));
48+
});
6749

68-
{ name: 'PomeloDrive', url: '/https://pomelodrive/url' },
69-
{
70-
name: 'OrangeDrive',
71-
url: ''
72-
},
73-
{
74-
name: 'TomatoDrive',
75-
url: ''
76-
},
77-
{
78-
name: '',
79-
url: 'superDrive/url'
80-
},
81-
{
82-
name: 'AvocadoDrive',
83-
url: ''
84-
}
85-
];
8650
let model = selectedDrivesModelMap.get(selectedDrives);
8751

88-
//const model = new DriveListModel(availableDrives, selectedDrives);
89-
9052
commands.addCommand(CommandIDs.openDrivesDialog, {
9153
execute: args => {
9254
const widget = tracker.currentWidget;
@@ -109,8 +71,8 @@ export const openDriveDialogPlugin: JupyterFrontEndPlugin<void> = {
10971
},
11072

11173
icon: driveBrowserIcon.bindprops({ stylesheet: 'menuItem' }),
112-
caption: trans.__('Add drives to filebrowser.'),
113-
label: trans.__('Add Drives To Filebrowser')
74+
caption: trans.__('Manage drives listed in filebrowser.'),
75+
label: trans.__('Manage listed drives')
11476
});
11577
}
11678
};

0 commit comments

Comments
 (0)