Skip to content

Commit

Permalink
GH-1905: Implemented the file download functionality.
Browse files Browse the repository at this point in the history
Closes #1905.

Signed-off-by: Akos Kitta <kittaakos@gmail.com>
  • Loading branch information
kittaakos committed Jun 15, 2018
1 parent 56b94e6 commit f03dde3
Show file tree
Hide file tree
Showing 19 changed files with 779 additions and 18 deletions.
3 changes: 2 additions & 1 deletion examples/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@theia/editor": "^0.3.11",
"@theia/editorconfig": "^0.3.11",
"@theia/extension-manager": "^0.3.11",
"@theia/file-download": "^0.3.11",
"@theia/file-search": "^0.3.11",
"@theia/filesystem": "^0.3.11",
"@theia/git": "^0.3.11",
Expand Down Expand Up @@ -53,4 +54,4 @@
"devDependencies": {
"@theia/cli": "^0.3.11"
}
}
}
3 changes: 2 additions & 1 deletion examples/electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@theia/editor": "^0.3.11",
"@theia/editorconfig": "^0.3.11",
"@theia/extension-manager": "^0.3.11",
"@theia/file-download": "^0.3.11",
"@theia/file-search": "^0.3.11",
"@theia/filesystem": "^0.3.11",
"@theia/git": "^0.3.11",
Expand Down Expand Up @@ -52,4 +53,4 @@
"devDependencies": {
"@theia/cli": "^0.3.11"
}
}
}
6 changes: 3 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"@theia/application-package": "^0.3.11",
"@types/body-parser": "^1.16.4",
"@types/bunyan": "^1.8.0",
"@types/express": "^4.0.36",
"@types/express": "^4.16.0",
"@types/lodash.debounce": "4.0.3",
"@types/lodash.throttle": "^4.1.3",
"@types/route-parser": "^0.1.1",
Expand All @@ -20,7 +20,7 @@
"bunyan": "^1.8.10",
"electron": "1.8.2-beta.5",
"es6-promise": "^4.2.4",
"express": "^4.15.3",
"express": "^4.16.3",
"file-icons-js": "^1.0.3",
"font-awesome": "^4.7.0",
"inversify": "^4.2.0",
Expand Down Expand Up @@ -80,4 +80,4 @@
"nyc": {
"extends": "../../configs/nyc.json"
}
}
}
12 changes: 12 additions & 0 deletions packages/core/src/common/objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ const _hasOwnProperty = Object.prototype.hasOwnProperty;
export function notEmpty<T>(arg: T | undefined | null): arg is T {
return arg !== undefined && arg !== null;
}

/**
* `true` if the argument is an empty object. Otherwise, `false`.
*/
export function isEmpty(arg: Object): boolean {
for (const key in arg) {
if (arg.hasOwnProperty(key)) {
return false;
}
}
return true;
}
29 changes: 29 additions & 0 deletions packages/file-download/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Theia - File Download

Provides the file download contribution to the `Files` navigator.

Supports single and multi file downloads.
- Single files will be downloaded as is.
- Folders will be downloaded az ZIP archives.
- When downloading multiple files, each file should be contained in the same parent folder. Otherwise, the command contribution is disabled.

### REST API

- To download a single file or folder use the following endpoint: `GET /file-download/?uri=/encoded/file/uri/to/the/resource`.
- Example: `curl -X GET http://localhost:3000/file-download/?uri=file:///Users/akos.kitta/git/theia/package.json`.

- To download multiple files (from the same folder) use the `PUT /file-download/` endpoint with the `application/json` content type header and the following body format:
```json
{
"uri": [
"/encoded/file/uri/to/the/resource",
"/another/encoded/file/file/uri/to/the/resource"
]
}
```
```
curl -X PUT -H "Content-Type: application/json" -d '{ "uris": ["file:///Users/akos.kitta/git/theia/package.json", "file:///Users/akos.kitta/git/theia/README.md"] }' http://localhost:3000/file-download/
```
## License
[Apache-2.0](https://github.com/theia-ide/theia/blob/master/LICENSE)
10 changes: 10 additions & 0 deletions packages/file-download/compile.tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../configs/base.tsconfig",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
},
"include": [
"src"
]
}
60 changes: 60 additions & 0 deletions packages/file-download/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "@theia/file-download",
"version": "0.3.11",
"description": "Theia - File Download Extension",
"dependencies": {
"@theia/core": "^0.3.11",
"@theia/filesystem": "^0.3.11",
"@theia/navigator": "^0.3.11",
"@theia/workspace": "^0.3.11",
"@types/body-parser": "^1.17.0",
"@types/mime-types": "^2.1.0",
"@types/rimraf": "^2.0.2",
"@types/uuid": "^3.4.3",
"body-parser": "^1.18.3",
"http-status-codes": "^1.3.0",
"mime-types": "^2.1.18",
"rimraf": "^2.6.2",
"uuid": "^3.2.1",
"zip-dir": "^1.0.2"
},
"publishConfig": {
"access": "public"
},
"theiaExtensions": [
{
"frontend": "lib/browser/file-download-frontend-module",
"backend": "lib/node/file-download-backend-module"
}
],
"keywords": [
"theia-extension"
],
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/theia-ide/theia.git"
},
"bugs": {
"url": "https://github.com/theia-ide/theia/issues"
},
"homepage": "https://github.com/theia-ide/theia",
"files": [
"lib",
"src"
],
"scripts": {
"prepare": "yarn run clean && yarn run build",
"clean": "theiaext clean",
"build": "theiaext build",
"watch": "theiaext watch",
"test": "theiaext test",
"docs": "theiaext docs"
},
"devDependencies": {
"@theia/ext-scripts": "^0.3.11"
},
"nyc": {
"extends": "../../configs/nyc.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2018 TypeFox and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import { inject, injectable } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { notEmpty } from '@theia/core/lib/common/objects';
import { UriSelection } from '@theia/core/lib/common/selection';
import { SelectionService } from '@theia/core/lib/common/selection-service';
import { Command, CommandContribution, CommandRegistry } from '@theia/core/lib/common/command';
import { UriAwareCommandHandler, UriCommandHandler } from '@theia/core/lib/common/uri-command-handler';
import { FileDownloadService } from './file-download-service';

@injectable()
export class FileDownloadCommandContribution implements CommandContribution {

@inject(FileDownloadService)
protected readonly downloadService: FileDownloadService;

@inject(SelectionService)
protected readonly selectionService: SelectionService;

registerCommands(registry: CommandRegistry): void {
const options = {
multi: true,
isValid: this.isValid.bind(this)
};
const handler = new UriAwareCommandHandler<URI[]>(this.selectionService, this.downloadHandler(), options);
registry.registerCommand(FileDownloadCommands.DOWNLOAD, handler);
}

protected downloadHandler(): UriCommandHandler<URI[]> {
return {
execute: uri => this.executeDownload(uri),
isEnabled: (uri, args) => this.isDownloadEnabled(uri, args),
isVisible: (uri, args) => this.isDownloadVisible(uri, args),
};
}

protected async executeDownload(uris: URI[]): Promise<void> {
this.downloadService.download(uris);
}

// tslint:disable-next-line:no-any
protected isDownloadEnabled(uri: Object | undefined, ...args: any[]): boolean {
return this.getUris(uri).length > 0;
}

// tslint:disable-next-line:no-any
protected isDownloadVisible(uri: Object | undefined, ...args: any[]): boolean {
return this.isDownloadEnabled(uri, args);
}

protected isValid(uris: URI[]): boolean {
if (uris.length === 0) {
return false;
}
if (uris.length === 1) {
return true;
}
// Can download multiple files iff they are from the same container folder.
const [firstUri, ...restUris] = uris;
const expectedParent = firstUri.parent.toString();
return restUris.every(u => u.parent.toString() === expectedParent);
}

protected getUris(uri: Object | undefined): URI[] {
if (uri === undefined) {
return [];
}
return (Array.isArray(uri) ? uri : [uri]).map(u => this.getUri(u)).filter(notEmpty);
}

protected getUri(uri: Object | undefined): URI | undefined {
if (uri instanceof URI) {
return uri;
}
if (UriSelection.is(uri)) {
return uri.uri;
}
return undefined;
}

}

export namespace FileDownloadCommands {

export const DOWNLOAD: Command = {
id: 'file.download'
};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2018 TypeFox and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import { ContainerModule } from 'inversify';
import { MenuContribution } from '@theia/core/lib/common/menu';
import { CommandContribution } from '@theia/core/lib/common/command';
import { FileDownloadService } from './file-download-service';
import { FileDownloadMenuContribution } from './file-download-menu-contribution';
import { FileDownloadCommandContribution } from './file-download-command-contribution';

export default new ContainerModule(bind => {
bind(FileDownloadService).toSelf().inSingletonScope();
bind(CommandContribution).to(FileDownloadCommandContribution).inSingletonScope();
bind(MenuContribution).to(FileDownloadMenuContribution).inSingletonScope();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2018 TypeFox and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import { injectable } from 'inversify';
import { MenuContribution, MenuModelRegistry } from '@theia/core/lib/common/menu';
import { NavigatorContextMenu } from '@theia/navigator/lib/browser/navigator-contribution';
import { FileDownloadCommands } from './file-download-command-contribution';

@injectable()
export class FileDownloadMenuContribution implements MenuContribution {

registerMenus(registry: MenuModelRegistry) {
registry.registerMenuAction(NavigatorContextMenu.MOVE, {
commandId: FileDownloadCommands.DOWNLOAD.id,
label: 'Download',
order: 'z' // Should be the last item in the menu group.
});
}

}
Loading

0 comments on commit f03dde3

Please sign in to comment.