mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-06-30 18:15:11 +00:00
[ACA-1894] - added the possibility to filter whenever is possible by … (#5633)
* [ACA-1894] - added the possibility to filter whenever is possible by file path * [ACA-1894] - fixed linting * [ACA-1894] - reverted dynamic app config service refresh * [ACA-1894] - added extra example in upload docs * Update upload.service.md Co-authored-by: Eugenio Romano <eromano@users.noreply.github.com>
This commit is contained in:
parent
b9842ba12b
commit
6fea3b8cdd
@ -463,6 +463,14 @@
|
||||
"nocase": true
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
"excluded": [
|
||||
".git"
|
||||
],
|
||||
"match-options": {
|
||||
"nocase": true
|
||||
}
|
||||
},
|
||||
"logLevel": "trace",
|
||||
"activiti": {
|
||||
"rest": {
|
||||
|
@ -87,6 +87,25 @@ node module.
|
||||
}
|
||||
}
|
||||
```
|
||||
From vesion 3.8.0 It's possible filter also for the folder whilst uploading a whole folder.
|
||||
|
||||
Note that all standard glob patterns work and you can end patterns with a forward
|
||||
slash `/` character to specify a directory.
|
||||
**app.config.json**
|
||||
|
||||
```json
|
||||
{
|
||||
"ecmHost": "http://localhost:3000/ecm",
|
||||
"bpmHost": "http://localhost:3000/bpm",
|
||||
"application": {
|
||||
"name": "Alfresco"
|
||||
},
|
||||
"folders": {
|
||||
"excluded": [".git"],
|
||||
"match-options": {
|
||||
"nocase": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this way all the files present in the .git folder won't be uploaded.
|
||||
Please note that the filtering options available for the folders is the same as the one for the files.
|
||||
|
@ -704,6 +704,102 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
"description": "Configuration of rules applied to file upload",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"excluded": {
|
||||
"description": "File exclusions",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"match-options": {
|
||||
"description": "Minimatch plugin option that will be applied for the check. By default all the options are false",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"debug": {
|
||||
"description": "Dump a ton of stuff to stderr",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"nobrace": {
|
||||
"description": "Do not expand {a,b} and {1..3} brace sets.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"noglobstar": {
|
||||
"description": "Disable ** matching against multiple folder names.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dot": {
|
||||
"description": "Allow patterns to match filenames starting with a period, even if the pattern does not explicitly have a period in that spot.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"noext": {
|
||||
"description": "Disable 'extglob' style patterns like +(a|b).",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"nocase": {
|
||||
"description": "Perform a case-insensitive match.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"nonull": {
|
||||
"description": "When a match is not found by minimatch.match, return a list containing the pattern itself if this option is set. When not set, an empty list is returned if there are no matches.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"matchBase": {
|
||||
"description": "If set, then patterns without slashes will be matched against the basename of the path if it contains slashes.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"nocomment": {
|
||||
"description": "Suppress the behavior of treating # at the start of a pattern as a comment.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"nonegate": {
|
||||
"description": "Suppress the behavior of treating a leading ! character as negation.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"flipNegate": {
|
||||
"description": "Returns from negate expressions the same as if they were not negated.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"logLevel": {
|
||||
"description": "Application's logging level",
|
||||
"type": "string",
|
||||
|
@ -47,6 +47,13 @@ describe('UploadService', () => {
|
||||
/* cspell:disable-next-line */
|
||||
nocase: true
|
||||
}
|
||||
},
|
||||
folders: {
|
||||
excluded: ['ROLLINGPANDA'],
|
||||
'match-options': {
|
||||
/* cspell:disable-next-line */
|
||||
nocase: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -319,6 +326,22 @@ describe('UploadService', () => {
|
||||
expect(result[0]).toBe(file4);
|
||||
});
|
||||
|
||||
it('should skip files if they are in an excluded folder', () => {
|
||||
const file1: any = { name: 'readmetoo.md', file : { webkitRelativePath: '/rollingPanda/' }};
|
||||
const file2: any = { name: 'readme.md', file : { webkitRelativePath: '/test/' }};
|
||||
const result = service.addToQueue(file1, file2);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0]).toBe(file2);
|
||||
});
|
||||
|
||||
it('should match the folder in case insensitive way', () => {
|
||||
const file1: any = { name: 'readmetoo.md', file : { webkitRelativePath: '/rollingPanda/' }};
|
||||
const file2: any = { name: 'readme.md', file : { webkitRelativePath: '/test/' }};
|
||||
const result = service.addToQueue(file1, file2);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0]).toBe(file2);
|
||||
});
|
||||
|
||||
it('should call onUploadDeleted if file was deleted', async(() => {
|
||||
const file = <any> ({ status: FileUploadStatus.Deleted });
|
||||
spyOn(service.fileUploadDeleted, 'next');
|
||||
|
@ -35,13 +35,14 @@ const MAX_CANCELLABLE_FILE_PERCENTAGE = 50;
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UploadService {
|
||||
|
||||
private cache: { [key: string]: any } = {};
|
||||
private totalComplete: number = 0;
|
||||
private totalAborted: number = 0;
|
||||
private totalError: number = 0;
|
||||
private excludedFileList: string[] = [];
|
||||
private excludedFoldersList: string[] = [];
|
||||
private matchingOptions: any = null;
|
||||
private folderMatchingOptions: any = null;
|
||||
private abortedFile: string;
|
||||
|
||||
activeTask: Promise<any> = null;
|
||||
@ -49,16 +50,31 @@ export class UploadService {
|
||||
|
||||
queueChanged: Subject<FileModel[]> = new Subject<FileModel[]>();
|
||||
fileUpload: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadStarting: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadCancelled: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadProgress: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadAborted: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadError: Subject<FileUploadErrorEvent> = new Subject<FileUploadErrorEvent>();
|
||||
fileUploadComplete: Subject<FileUploadCompleteEvent> = new Subject<FileUploadCompleteEvent>();
|
||||
fileUploadDeleted: Subject<FileUploadDeleteEvent> = new Subject<FileUploadDeleteEvent>();
|
||||
fileUploadStarting: Subject<FileUploadEvent> = new Subject<
|
||||
FileUploadEvent
|
||||
>();
|
||||
fileUploadCancelled: Subject<FileUploadEvent> = new Subject<
|
||||
FileUploadEvent
|
||||
>();
|
||||
fileUploadProgress: Subject<FileUploadEvent> = new Subject<
|
||||
FileUploadEvent
|
||||
>();
|
||||
fileUploadAborted: Subject<FileUploadEvent> = new Subject<
|
||||
FileUploadEvent
|
||||
>();
|
||||
fileUploadError: Subject<FileUploadErrorEvent> = new Subject<
|
||||
FileUploadErrorEvent
|
||||
>();
|
||||
fileUploadComplete: Subject<FileUploadCompleteEvent> = new Subject<
|
||||
FileUploadCompleteEvent
|
||||
>();
|
||||
fileUploadDeleted: Subject<FileUploadDeleteEvent> = new Subject<
|
||||
FileUploadDeleteEvent
|
||||
>();
|
||||
fileDeleted: Subject<string> = new Subject<string>();
|
||||
|
||||
constructor(protected apiService: AlfrescoApiService, private appConfigService: AppConfigService) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,35 +99,66 @@ export class UploadService {
|
||||
* @returns Array of files that were not blocked from upload by the ignore list
|
||||
*/
|
||||
addToQueue(...files: FileModel[]): FileModel[] {
|
||||
const allowedFiles = files.filter((currentFile) => this.filterElement(currentFile));
|
||||
const allowedFiles = files.filter((currentFile) =>
|
||||
this.filterElement(currentFile)
|
||||
);
|
||||
this.queue = this.queue.concat(allowedFiles);
|
||||
this.queueChanged.next(this.queue);
|
||||
return allowedFiles;
|
||||
}
|
||||
|
||||
private filterElement(file: FileModel) {
|
||||
this.excludedFileList = <string[]> this.appConfigService.get('files.excluded');
|
||||
this.excludedFoldersList = <string[]> this.appConfigService.get('folders.excluded');
|
||||
let isAllowed = true;
|
||||
|
||||
this.excludedFileList = <string[]> this.appConfigService.get('files.excluded');
|
||||
if (this.excludedFileList) {
|
||||
|
||||
this.matchingOptions = this.appConfigService.get('files.match-options');
|
||||
isAllowed = this.isFileNameAllowed(file);
|
||||
}
|
||||
|
||||
isAllowed = this.excludedFileList.filter((pattern) => {
|
||||
const minimatch = new Minimatch(pattern, this.matchingOptions);
|
||||
return minimatch.match(file.name);
|
||||
}).length === 0;
|
||||
if (isAllowed && this.excludedFoldersList) {
|
||||
this.folderMatchingOptions = this.appConfigService.get('folders.match-options');
|
||||
isAllowed = this.isParentFolderAllowed(file);
|
||||
}
|
||||
return isAllowed;
|
||||
}
|
||||
|
||||
private isParentFolderAllowed(file: FileModel): boolean {
|
||||
let isAllowed: boolean = true;
|
||||
const currentFile: any = file.file;
|
||||
if (currentFile && currentFile.webkitRelativePath) {
|
||||
isAllowed =
|
||||
this.excludedFoldersList.filter((folderToExclude) => {
|
||||
return currentFile.webkitRelativePath
|
||||
.split('/')
|
||||
.some((pathElement) => {
|
||||
const minimatch = new Minimatch(folderToExclude, this.folderMatchingOptions);
|
||||
return minimatch.match(pathElement);
|
||||
});
|
||||
}).length === 0;
|
||||
}
|
||||
return isAllowed;
|
||||
}
|
||||
|
||||
private isFileNameAllowed(file: FileModel): boolean {
|
||||
return (
|
||||
this.excludedFileList.filter((pattern) => {
|
||||
const minimatch = new Minimatch(pattern, this.matchingOptions);
|
||||
return minimatch.match(file.name);
|
||||
}).length === 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all the files in the queue that are not yet uploaded and uploads them into the directory folder.
|
||||
* @param emitter Emitter to invoke on file status change
|
||||
*/
|
||||
uploadFilesInTheQueue(emitter?: EventEmitter<any>): void {
|
||||
if (!this.activeTask) {
|
||||
const file = this.queue.find((currentFile) => currentFile.status === FileUploadStatus.Pending);
|
||||
const file = this.queue.find(
|
||||
(currentFile) => currentFile.status === FileUploadStatus.Pending
|
||||
);
|
||||
if (file) {
|
||||
this.onUploadStarting(file);
|
||||
|
||||
@ -150,7 +197,6 @@ export class UploadService {
|
||||
this.abortedFile = file.name;
|
||||
delete this.cache[file.name];
|
||||
promise.next();
|
||||
|
||||
} else {
|
||||
const performAction = this.getAction(file);
|
||||
performAction();
|
||||
@ -191,28 +237,28 @@ export class UploadService {
|
||||
}
|
||||
|
||||
if (file.id) {
|
||||
return this.apiService.getInstance().node.updateNodeContent(
|
||||
file.id,
|
||||
file.file,
|
||||
opts
|
||||
);
|
||||
return this.apiService
|
||||
.getInstance()
|
||||
.node.updateNodeContent(file.id, file.file, opts);
|
||||
} else {
|
||||
return this.apiService.getInstance().upload.uploadFile(
|
||||
file.file,
|
||||
file.options.path,
|
||||
file.options.parentId,
|
||||
file.options,
|
||||
opts
|
||||
);
|
||||
return this.apiService
|
||||
.getInstance()
|
||||
.upload.uploadFile(
|
||||
file.file,
|
||||
file.options.path,
|
||||
file.options.parentId,
|
||||
file.options,
|
||||
opts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private beginUpload(file: FileModel, emitter: EventEmitter<any>): any {
|
||||
|
||||
const promise = this.getUploadPromise(file);
|
||||
promise.on('progress', (progress: FileUploadProgress) => {
|
||||
this.onUploadProgress(file, progress);
|
||||
})
|
||||
promise
|
||||
.on('progress', (progress: FileUploadProgress) => {
|
||||
this.onUploadProgress(file, progress);
|
||||
})
|
||||
.on('abort', () => {
|
||||
this.onUploadAborted(file);
|
||||
if (emitter) {
|
||||
@ -239,7 +285,7 @@ export class UploadService {
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => { });
|
||||
.catch(() => {});
|
||||
|
||||
return promise;
|
||||
}
|
||||
@ -253,7 +299,10 @@ export class UploadService {
|
||||
}
|
||||
}
|
||||
|
||||
private onUploadProgress(file: FileModel, progress: FileUploadProgress): void {
|
||||
private onUploadProgress(
|
||||
file: FileModel,
|
||||
progress: FileUploadProgress
|
||||
): void {
|
||||
if (file) {
|
||||
file.progress = progress;
|
||||
file.status = FileUploadStatus.Progress;
|
||||
@ -275,7 +324,11 @@ export class UploadService {
|
||||
delete this.cache[file.name];
|
||||
}
|
||||
|
||||
const event = new FileUploadErrorEvent(file, error, this.totalError);
|
||||
const event = new FileUploadErrorEvent(
|
||||
file,
|
||||
error,
|
||||
this.totalError
|
||||
);
|
||||
this.fileUpload.next(event);
|
||||
this.fileUploadError.next(event);
|
||||
}
|
||||
@ -291,7 +344,12 @@ export class UploadService {
|
||||
delete this.cache[file.name];
|
||||
}
|
||||
|
||||
const event = new FileUploadCompleteEvent(file, this.totalComplete, data, this.totalAborted);
|
||||
const event = new FileUploadCompleteEvent(
|
||||
file,
|
||||
this.totalComplete,
|
||||
data,
|
||||
this.totalAborted
|
||||
);
|
||||
this.fileUpload.next(event);
|
||||
this.fileUploadComplete.next(event);
|
||||
}
|
||||
@ -340,12 +398,16 @@ export class UploadService {
|
||||
}
|
||||
|
||||
private deleteAbortedNode(nodeId: string) {
|
||||
this.apiService.getInstance().core.nodesApi.deleteNode(nodeId, { permanent: true })
|
||||
.then(() => this.abortedFile = undefined);
|
||||
|
||||
this.apiService
|
||||
.getInstance()
|
||||
.core.nodesApi.deleteNode(nodeId, { permanent: true })
|
||||
.then(() => (this.abortedFile = undefined));
|
||||
}
|
||||
|
||||
private isSaveToAbortFile(file: FileModel): boolean {
|
||||
return file.size > MIN_CANCELLABLE_FILE_SIZE && file.progress.percent < MAX_CANCELLABLE_FILE_PERCENTAGE;
|
||||
return (
|
||||
file.size > MIN_CANCELLABLE_FILE_SIZE &&
|
||||
file.progress.percent < MAX_CANCELLABLE_FILE_PERCENTAGE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user