import { FileTree } from '../file-tree/file-tree';
import { IconButton, Grid, Typography, StyleRulesCallback, withStyles, WithStyles, CardHeader, CardContent, Card, Button } from '@material-ui/core';
import { CustomizeTableIcon } from '../icon/icon';
+import { connect, DispatchProp } from "react-redux";
+import { Dispatch } from "redux";
+import { RootState } from "../../store/store";
+import { ServiceRepository } from "../../services/services";
export interface CollectionPanelFilesProps {
items: Array<TreeItem<FileTreeData>>;
}
});
-export const CollectionPanelFiles = withStyles(styles)(
- ({ onItemMenuOpen, onOptionsMenuOpen, classes, ...treeProps }: CollectionPanelFilesProps & WithStyles<CssRules>) =>
+const renameFile = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ services.collectionFilesService.renameTest();
+};
+
+
+export const CollectionPanelFiles =
+ connect()(
+ withStyles(styles)(
+ ({ onItemMenuOpen, onOptionsMenuOpen, classes, dispatch, ...treeProps }: CollectionPanelFilesProps & DispatchProp & WithStyles<CssRules>) =>
<Card className={classes.root}>
<CardHeader
title="Files"
action={
- <Button
- variant='raised'
+ <Button onClick={
+ () => {
+ dispatch<any>(renameFile());
+ }}
+ variant='raised'
color='primary'
size='small'>
Upload data
</Typography>
</Grid>
<FileTree onMenuOpen={onItemMenuOpen} {...treeProps} />
- </Card>);
+ </Card>)
+);
}
export interface CollectionDirectory {
- parentId: string;
+ path: string;
id: string;
name: string;
type: CollectionFileType.DIRECTORY;
}
export interface CollectionFile {
- parentId: string;
+ path: string;
id: string;
name: string;
size: number;
export const createCollectionDirectory = (data: Partial<CollectionDirectory>): CollectionDirectory => ({
id: '',
name: '',
- parentId: '',
+ path: '',
type: CollectionFileType.DIRECTORY,
...data
});
export const createCollectionFile = (data: Partial<CollectionFile>): CollectionFile => ({
id: '',
name: '',
- parentId: '',
+ path: '',
size: 0,
type: CollectionFileType.FILE,
...data
// SPDX-License-Identifier: AGPL-3.0
import { CollectionService } from "../collection-service/collection-service";
-import { parseKeepManifestText } from "./collection-manifest-parser";
+import { parseKeepManifestText, stringifyKeepManifest } from "./collection-manifest-parser";
import { mapManifestToCollectionFilesTree } from "./collection-manifest-mapper";
+import { CollectionFile } from "../../models/collection-file";
+import { CommonResourceService } from "../../common/api/common-resource-service";
+import * as _ from "lodash";
export class CollectionFilesService {
-
+
constructor(private collectionService: CollectionService) { }
getFiles(collectionUuid: string) {
);
}
-}
\ No newline at end of file
+ async renameFile(collectionUuid: string, file: { name: string, path: string }, newName: string) {
+ const collection = await this.collectionService.get(collectionUuid);
+ const manifest = parseKeepManifestText(collection.manifestText);
+ const updatedManifest = manifest.map(
+ stream => stream.name === file.path
+ ? {
+ ...stream,
+ files: stream.files.map(
+ f => f.name === file.name
+ ? { ...f, name: newName }
+ : f
+ )
+ }
+ : stream
+ );
+ const manifestText = stringifyKeepManifest(updatedManifest);
+ const data = { ...collection, manifestText };
+ return this.collectionService.update(collectionUuid, CommonResourceService.mapKeys(_.snakeCase)(data));
+ }
+
+ async deleteFile(collectionUuid: string, file: { name: string, path: string }) {
+ const collection = await this.collectionService.get(collectionUuid);
+ const manifest = parseKeepManifestText(collection.manifestText);
+ const updatedManifest = manifest.map(stream =>
+ stream.name === file.path
+ ? {
+ ...stream,
+ files: stream.files.filter(f => f.name !== file.name)
+ }
+ : stream
+ );
+ const manifestText = stringifyKeepManifest(updatedManifest);
+ return this.collectionService.update(collectionUuid, { manifestText });
+ }
+
+ renameTest() {
+ const u = this.renameFile('qr1hi-4zz18-n0sx074erl4p0ph', {
+ name: 'extracted2.txt.png',
+ path: ''
+ }, 'extracted-new.txt.png');
+ }
+}
// SPDX-License-Identifier: AGPL-3.0
import { parseKeepManifestText } from "./collection-manifest-parser";
-import { mapManifestToFiles, mapManifestToDirectories } from "./collection-manifest-mapper";
+import { mapManifestToFiles, mapManifestToDirectories, mapManifestToCollectionFilesTree, mapCollectionFilesTreeToManifest } from "./collection-manifest-mapper";
test('mapManifestToFiles', () => {
const manifestText = `. 930625b054ce894ac40596c3f5a0d947+33 0:0:a 0:0:b 0:33:output.txt\n./c d41d8cd98f00b204e9800998ecf8427e+0 0:0:d`;
const manifest = parseKeepManifestText(manifestText);
const files = mapManifestToFiles(manifest);
expect(files).toEqual([{
- parentId: '',
+ path: '',
id: '/a',
name: 'a',
size: 0,
type: 'file'
}, {
- parentId: '',
+ path: '',
id: '/b',
name: 'b',
size: 0,
type: 'file'
}, {
- parentId: '',
+ path: '',
id: '/output.txt',
name: 'output.txt',
size: 33,
type: 'file'
}, {
- parentId: '/c',
+ path: '/c',
id: '/c/d',
name: 'd',
size: 0,
const manifest = parseKeepManifestText(manifestText);
const directories = mapManifestToDirectories(manifest);
expect(directories).toEqual([{
- parentId: "",
+ path: "",
id: '/c',
name: 'c',
type: 'directory'
}, {
- parentId: '/c',
+ path: '/c',
id: '/c/user',
name: 'user',
type: 'directory'
}, {
- parentId: '/c/user',
+ path: '/c/user',
id: '/c/user/results',
name: 'results',
type: 'directory'
},]);
+});
+
+test('mapCollectionFilesTreeToManifest', () => {
+ const manifestText = `. 930625b054ce894ac40596c3f5a0d947+33 0:22:test.txt\n./c/user/results 930625b054ce894ac40596c3f5a0d947+33 0:0:a 0:0:b 0:33:output.txt\n`;
+ const tree = mapManifestToCollectionFilesTree(parseKeepManifestText(manifestText));
+ const manifest = mapCollectionFilesTreeToManifest(tree);
+ expect(manifest).toEqual([{
+ name: '',
+ locators: [],
+ files: [{
+ name: 'test.txt',
+ position: '',
+ size: 22
+ },],
+ }, {
+ name: '/c/user/results',
+ locators: [],
+ files: [{
+ name: 'a',
+ position: '',
+ size: 0
+ }, {
+ name: 'b',
+ position: '',
+ size: 0
+ }, {
+ name: 'output.txt',
+ position: '',
+ size: 33
+ },],
+ },]);
+
});
\ No newline at end of file
//
// SPDX-License-Identifier: AGPL-3.0
-import { uniqBy } from 'lodash';
+import { uniqBy, groupBy } from 'lodash';
import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "../../models/keep-manifest";
-import { TreeNode, setNode, createTree } from '../../models/tree';
-import { CollectionFilesTree, CollectionFile, CollectionDirectory, createCollectionDirectory, createCollectionFile } from '../../models/collection-file';
+import { TreeNode, setNode, createTree, getNodeDescendants, getNodeValue } from '../../models/tree';
+import { CollectionFilesTree, CollectionFile, CollectionDirectory, createCollectionDirectory, createCollectionFile, CollectionFileType } from '../../models/collection-file';
+
+export const mapCollectionFilesTreeToManifest = (tree: CollectionFilesTree): KeepManifest => {
+ const values = getNodeDescendants('')(tree).map(id => getNodeValue(id)(tree));
+ const files = values.filter(value => value && value.type === CollectionFileType.FILE) as CollectionFile[];
+ const fileGroups = groupBy(files, file => file.path);
+ return Object
+ .keys(fileGroups)
+ .map(dirName => ({
+ name: dirName,
+ locators: [],
+ files: fileGroups[dirName].map(mapCollectionFile)
+ }));
+};
export const mapManifestToCollectionFilesTree = (manifest: KeepManifest): CollectionFilesTree =>
manifestToCollectionFiles(manifest)
export const mapCollectionFileToTreeNode = (file: CollectionFile): TreeNode<CollectionFile> => ({
children: [],
id: file.id,
- parent: file.parentId,
+ parent: file.path,
value: file
});
const mapPathComponentToDirectory = (component: string, index: number, components: string[]): CollectionDirectory =>
createCollectionDirectory({
- parentId: index === 0 ? '' : joinPathComponents(components, index),
+ path: index === 0 ? '' : joinPathComponents(components, index),
id: joinPathComponents(components, index + 1),
name: component,
});
const joinPathComponents = (components: string[], index: number) =>
`/${components.slice(0, index).join('/')}`;
+const mapCollectionFile = (file: CollectionFile): KeepManifestStreamFile => ({
+ name: file.name,
+ position: '',
+ size: file.size
+});
+
const mapStreamDirectory = (stream: KeepManifestStream): CollectionDirectory =>
createCollectionDirectory({
- parentId: '',
+ path: '',
id: stream.name,
name: stream.name,
});
const mapStreamFile = (stream: KeepManifestStream) =>
(file: KeepManifestStreamFile): CollectionFile =>
createCollectionFile({
- parentId: stream.name,
+ path: stream.name,
id: `${stream.name}/${file.name}`,
name: file.name,
size: file.size,
//
// SPDX-License-Identifier: AGPL-3.0
-import { parseKeepManifestText, parseKeepManifestStream } from "./collection-manifest-parser";
+import { parseKeepManifestText, parseKeepManifestStream, stringifyKeepManifest } from "./collection-manifest-parser";
describe('parseKeepManifestText', () => {
it('should parse text into streams', () => {
});
it('should parse stream files', () => {
expect(stream.files).toEqual([
- {name: 'a', position: '0', size: 0},
- {name: 'b', position: '0', size: 0},
- {name: 'output.txt', position: '0', size: 33},
+ { name: 'a', position: '0', size: 0 },
+ { name: 'b', position: '0', size: 0 },
+ { name: 'output.txt', position: '0', size: 33 },
]);
});
+});
+
+test('stringifyKeepManifest', () => {
+ const manifestText = `. 930625b054ce894ac40596c3f5a0d947+33 0:22:test.txt\n./c/user/results 930625b054ce894ac40596c3f5a0d947+33 0:0:a 0:0:b 0:33:output.txt\n`;
+ const manifest = parseKeepManifestText(manifestText);
+ expect(stringifyKeepManifest(manifest)).toEqual(`. 930625b054ce894ac40596c3f5a0d947+33 0:22:test.txt\n./c/user/results 930625b054ce894ac40596c3f5a0d947+33 0:0:a 0:0:b 0:33:output.txt\n`);
});
\ No newline at end of file
//
// SPDX-License-Identifier: AGPL-3.0
-import { KeepManifestStream, KeepManifestStreamFile } from "../../models/keep-manifest";
+import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "../../models/keep-manifest";
/**
* Documentation [http://doc.arvados.org/api/storage.html](http://doc.arvados.org/api/storage.html)
};
};
+export const stringifyKeepManifest = (manifest: KeepManifest) =>
+ manifest.map(stringifyKeepManifestStream).join('');
+
+export const stringifyKeepManifestStream = (stream: KeepManifestStream) =>
+ `.${stream.name} ${stream.locators.join(' ')} ${stream.files.map(stringifyFile).join(' ')}\n`;
+
const FILE_LOCATOR_REGEXP = /^([0-9a-f]{32})\+([0-9]+)(\+[A-Z][-A-Za-z0-9@_]*)*$/;
const FILE_REGEXP = /([0-9]+):([0-9]+):(.*)/;
const match = FILE_REGEXP.exec(token);
const [position, size, name] = match!.slice(1);
return { name, position, size: parseInt(size, 10) };
-};
\ No newline at end of file
+};
+
+const stringifyFile = (file: KeepManifestStreamFile) =>
+ `${file.position}:${file.size}:${file.name}`;
\ No newline at end of file
describe('CollectionPanelFilesReducer', () => {
const files: Array<CollectionFile | CollectionDirectory> = [
- createCollectionDirectory({ id: 'Directory 1', name: 'Directory 1', parentId: '' }),
- createCollectionDirectory({ id: 'Directory 2', name: 'Directory 2', parentId: 'Directory 1' }),
- createCollectionDirectory({ id: 'Directory 3', name: 'Directory 3', parentId: '' }),
- createCollectionDirectory({ id: 'Directory 4', name: 'Directory 4', parentId: 'Directory 3' }),
- createCollectionFile({ id: 'file1.txt', name: 'file1.txt', parentId: 'Directory 2' }),
- createCollectionFile({ id: 'file2.txt', name: 'file2.txt', parentId: 'Directory 2' }),
- createCollectionFile({ id: 'file3.txt', name: 'file3.txt', parentId: 'Directory 3' }),
- createCollectionFile({ id: 'file4.txt', name: 'file4.txt', parentId: 'Directory 3' }),
- createCollectionFile({ id: 'file5.txt', name: 'file5.txt', parentId: 'Directory 4' }),
+ createCollectionDirectory({ id: 'Directory 1', name: 'Directory 1', path: '' }),
+ createCollectionDirectory({ id: 'Directory 2', name: 'Directory 2', path: 'Directory 1' }),
+ createCollectionDirectory({ id: 'Directory 3', name: 'Directory 3', path: '' }),
+ createCollectionDirectory({ id: 'Directory 4', name: 'Directory 4', path: 'Directory 3' }),
+ createCollectionFile({ id: 'file1.txt', name: 'file1.txt', path: 'Directory 2' }),
+ createCollectionFile({ id: 'file2.txt', name: 'file2.txt', path: 'Directory 2' }),
+ createCollectionFile({ id: 'file3.txt', name: 'file3.txt', path: 'Directory 3' }),
+ createCollectionFile({ id: 'file4.txt', name: 'file4.txt', path: 'Directory 3' }),
+ createCollectionFile({ id: 'file5.txt', name: 'file5.txt', path: 'Directory 4' }),
];
const collectionFilesTree = files.reduce((tree, file) => setNode({
children: [],
id: file.id,
- parent: file.parentId,
+ parent: file.path,
value: file
})(tree), createTree<CollectionFile | CollectionDirectory>());
it('SET_COLLECTION_FILES', () => {
expect(getNodeValue('Directory 1')(collectionPanelFilesTree)).toEqual({
- ...createCollectionDirectory({ id: 'Directory 1', name: 'Directory 1', parentId: '' }),
+ ...createCollectionDirectory({ id: 'Directory 1', name: 'Directory 1', path: '' }),
collapsed: true,
selected: false
});