Skip to content

Commit 0ac21c0

Browse files
committed
feat!: replace existing user file(-system) abstraction with a new one
The new abstraction uniquely identifies users and each file system. Right now, file systems are still hard coded (including their IDs). With this, I also introduce the Apollo-URLs (`apollo://`), that can be used to identify files across file systems or even users. This lays the foundation for file sharing with other users and much more.
1 parent 2ef1518 commit 0ac21c0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+918
-858
lines changed

src/AbstractUser.ts

-45
This file was deleted.

src/FileSearch.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import IUserFile from './files/IUserFile';
1+
import VirtualFile from './user/files/VirtualFile';
22

33
export default class FileSearch {
4-
static async searchFile(file: IUserFile, query: string): Promise<IUserFile[]> {
4+
static async searchFile(file: VirtualFile, query: string): Promise<VirtualFile[]> {
55
if (!await file.isDirectory()) {
66
throw new Error('File is not a directory');
77
}
88

99
return this.searchFileRecursive(await file.getFiles(), query);
1010
}
1111

12-
private static async searchFileRecursive(files: IUserFile[], query: string): Promise<IUserFile[]> {
12+
private static async searchFileRecursive(files: VirtualFile[], query: string): Promise<VirtualFile[]> {
1313
const result = [];
1414

1515
for (const file of files) {
16-
if (file.getName().toLowerCase().includes(query.toLowerCase())) {
16+
if (file.getFileName().toLowerCase().includes(query.toLowerCase())) {
1717
result.push(file);
1818
}
1919

src/IUserStorage.ts

-8
This file was deleted.

src/ThumbnailGenerator.ts

+8-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import Path from 'node:path';
22
import sharp, { Sharp } from 'sharp';
3-
import IUserFile from './files/IUserFile';
43
import VideoAnalyser from './media/video/analyser/VideoAnalyser';
54
import ProcessBuilder from './process_manager/ProcessBuilder';
5+
import LocalFile from './user/files/local/LocalFile';
66

77
export default class ThumbnailGenerator {
88
readonly sharpMimeTypes: string[];
@@ -54,7 +54,7 @@ export default class ThumbnailGenerator {
5454
* FIXME: Everything below has been copied from NASWeb, the predecessor of Apollo – This needs heavy refactoring :p
5555
*/
5656

57-
async generateThumbnail(file: IUserFile): Promise<{ mime: string, data: Buffer } | null> {
57+
async generateThumbnail(file: LocalFile): Promise<{ mime: string, data: Buffer } | null> {
5858
const mimeType = await file.getMimeType();
5959

6060
if (mimeType == null) {
@@ -110,9 +110,8 @@ export default class ThumbnailGenerator {
110110
};
111111
}
112112

113-
private async extractVideoCover(file: IUserFile): Promise<Sharp | null> {
114-
const filePath = await file.getAbsolutePathOnHost();
115-
if (filePath == null) throw new Error('filePath is null');
113+
private async extractVideoCover(file: LocalFile): Promise<Sharp | null> {
114+
const filePath = file.getAbsolutePathOnHost();
116115

117116
const videoStreams = await VideoAnalyser.analyze(filePath, true);
118117
const potentialCoverStreams = videoStreams.streams.filter(stream => stream.codecType == 'video' && stream.avgFrameRate == '0/0');
@@ -135,11 +134,8 @@ export default class ThumbnailGenerator {
135134

136135
// FIXME: Delete cwd when done
137136
// FIXME: write lock on cwd
138-
const cwdFile = await file.getOwner().getTmpFileSystem().createTmpDir('thumbnail-');
137+
const cwdFile = await file.fileSystem.owner.getTmpFileSystem().createTmpDir('thumbnail-');
139138
const cwd = cwdFile.getAbsolutePathOnHost();
140-
if (cwd == null) {
141-
throw new Error('cwd is null');
142-
}
143139

144140
const args = ['-i', filePath, '-map', coverStream.streamSpecifier, '-frames', '1', '-f', 'image2', coverStream.fileName];
145141

@@ -155,12 +151,10 @@ export default class ThumbnailGenerator {
155151
return sharp(Path.join(cwd, coverStream.fileName));
156152
}
157153

158-
private static async generateVideoThumbnail(file: IUserFile, sampleSize: number = 3, width = 500): Promise<{ img: Sharp }> {
154+
private static async generateVideoThumbnail(file: LocalFile, sampleSize: number = 3, width = 500): Promise<{ img: Sharp }> {
159155
if (sampleSize <= 0) throw new Error('sampleSize has to be positive');
160156

161-
const filePath = await file.getAbsolutePathOnHost();
162-
163-
if (filePath == null) throw new Error('filePath is null');
157+
const filePath = file.getAbsolutePathOnHost();
164158

165159
let videoDuration = 0;
166160

@@ -183,11 +177,8 @@ export default class ThumbnailGenerator {
183177

184178
// FIXME: Delete cwd when done
185179
// FIXME: write lock on cwd
186-
const cwdFile = await file.getOwner().getTmpFileSystem().createTmpDir('thumbnail-');
180+
const cwdFile = await file.fileSystem.owner.getTmpFileSystem().createTmpDir('thumbnail-');
187181
const cwd = cwdFile.getAbsolutePathOnHost();
188-
if (cwd == null) {
189-
throw new Error('cwd is null');
190-
}
191182

192183
const args = ['-i', filePath];
193184
args.unshift('-ss', Math.floor(0.1 * videoDuration).toString(), '-noaccurate_seek');

src/UserFileHelper.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import IUserFile from './files/IUserFile';
1+
import VirtualFile from './user/files/VirtualFile';
22

33
export default class UserFileHelper {
4-
static findFolderPoster(directory: IUserFile): Promise<IUserFile | null> {
4+
static findFolderPoster(directory: VirtualFile): Promise<VirtualFile | null> {
55
return UserFileHelper.findFirstFileMatchingInDirectory(directory, /^(?:poster|folder|cover)\.(?:png|jpg|jpeg|webp)$/ig);
66
}
77

8-
static async findFirstFileMatchingInDirectory(directory: IUserFile, fileNamePattern: RegExp): Promise<IUserFile | null> {
8+
static async findFirstFileMatchingInDirectory(directory: VirtualFile, fileNamePattern: RegExp): Promise<VirtualFile | null> {
99
const files = await directory.getFiles(); // TODO: Replace with a walk
1010
for (const file of files) {
11-
if (fileNamePattern.test(file.getName())) {
11+
if (fileNamePattern.test(file.getFileName())) {
1212
return file;
1313
}
1414
}

src/UserStorage.ts

-66
This file was deleted.

src/Utils.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import express from 'express';
22
import Fs from 'node:fs';
33
import Path from 'node:path';
4-
import IUserFile from './files/IUserFile';
4+
import VirtualFile from './user/files/VirtualFile';
55

66
export default class Utils {
7-
static async sendFileRespectingRequestedRange(req: express.Request, res: express.Response, next: express.NextFunction, file: string | IUserFile, mimeType: string, sendAsAttachment: boolean = false): Promise<void> {
7+
static async sendFileRespectingRequestedRange(req: express.Request, res: express.Response, next: express.NextFunction, file: string | VirtualFile, mimeType: string, sendAsAttachment: boolean = false): Promise<void> {
88
let fileStat: Fs.Stats;
99

1010
if (typeof file === 'string') {
@@ -53,7 +53,7 @@ export default class Utils {
5353
res.setHeader('Accept-Ranges', 'bytes');
5454

5555
if (sendAsAttachment) {
56-
res.setHeader('Content-Disposition', `attachment; filename="${Utils.tryReplacingBadCharactersForFileName(typeof file == 'string' ? Path.basename(file) : file.getName())}"`);
56+
res.setHeader('Content-Disposition', `attachment; filename="${Utils.tryReplacingBadCharactersForFileName(typeof file == 'string' ? Path.basename(file) : file.getFileName())}"`);
5757
}
5858

5959
res.setHeader('Content-Length', fileSize);

src/cache/FileStatCache.ts

+11-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import NodeCache from 'node-cache';
22
import Fs from 'node:fs';
3-
import IUserFile from '../files/IUserFile';
3+
import LocalFile from '../user/files/local/LocalFile';
44

55
export default class FileStatCache {
66
private readonly cache: NodeCache;
@@ -16,43 +16,42 @@ export default class FileStatCache {
1616
this.cache.flushAll();
1717
}
1818

19-
async clearFile(file: IUserFile): Promise<void> {
19+
async clearFile(file: LocalFile): Promise<void> {
2020
const cacheClear = Promise.all([
2121
this.getCacheKey(file, 'stat').then(key => this.cache.del(key)),
2222
this.getCacheKey(file, 'mimeType').then(key => this.cache.del(key))
2323
]);
2424

25-
if (file.getPath().lastIndexOf('/') === 0) {
25+
if (file.path.lastIndexOf('/') === 0) {
2626
this.cache.keys()
2727
.filter(key => key.endsWith(`:directorySize`))
2828
.forEach(key => this.cache.del(key));
2929
} else {
30-
const subdirPath = file.getFileSystem().getFile(file.getPath().substring(0, file.getPath().indexOf('/', 1) + 1)).getAbsolutePathOnHost();
30+
const subDirPath = file.fileSystem.getFile(file.path.substring(0, file.path.indexOf('/', 1) + 1)).getAbsolutePathOnHost();
3131

32-
if (subdirPath != null) {
32+
if (subDirPath != null) {
3333
this.cache.keys()
34-
.filter(key => key.startsWith(subdirPath))
34+
.filter(key => key.startsWith(subDirPath))
3535
.forEach(key => this.cache.del(key));
3636
}
3737
}
3838

39-
4039
await cacheClear;
4140
}
4241

43-
async setStat(file: IUserFile, stat: Fs.Stats): Promise<void> {
42+
async setStat(file: LocalFile, stat: Fs.Stats): Promise<void> {
4443
this.cache.set(await this.getCacheKey(file, 'stat'), stat);
4544
}
4645

47-
async getStat(file: IUserFile): Promise<Fs.Stats | undefined> {
46+
async getStat(file: LocalFile): Promise<Fs.Stats | undefined> {
4847
return this.cache.get(await this.getCacheKey(file, 'stat'));
4948
}
5049

51-
async setMimeType(file: IUserFile, mimeType: string | null): Promise<void> {
50+
async setMimeType(file: LocalFile, mimeType: string | null): Promise<void> {
5251
this.cache.set(await this.getCacheKey(file, 'mimeType'), mimeType);
5352
}
5453

55-
async getMimeType(file: IUserFile): Promise<string | null | undefined> {
54+
async getMimeType(file: LocalFile): Promise<string | null | undefined> {
5655
return this.cache.get(await this.getCacheKey(file, 'mimeType'));
5756
}
5857

@@ -64,7 +63,7 @@ export default class FileStatCache {
6463
return this.cache.get(`${filePath}:directorySize`);
6564
}
6665

67-
private async getCacheKey(file: IUserFile, type: 'stat' | 'mimeType'): Promise<string> {
66+
private async getCacheKey(file: LocalFile, type: 'stat' | 'mimeType'): Promise<string> {
6867
return `${await file.generateCacheId()}:${type}`;
6968
}
7069
}

src/cache/FileSystemBasedCache.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import Crypto from 'node:crypto';
22
import Fs from 'node:fs';
33
import Path from 'node:path';
4-
import AbstractUser from '../AbstractUser';
54
import { getAppTmpDir } from '../Constants';
5+
import ApolloUser from '../user/ApolloUser';
66

77
export default class FileSystemBasedCache {
88
private static INSTANCE: FileSystemBasedCache;
99

1010
protected constructor() {
1111
}
1212

13-
async setUserAssociatedCachedFile(user: AbstractUser, cacheSubKey: string, data: Buffer): Promise<void> {
13+
async setUserAssociatedCachedFile(user: ApolloUser, cacheSubKey: string, data: Buffer): Promise<void> {
1414
const cacheFilePath = this.generateUserAssociatedCacheFilePath(user, cacheSubKey);
1515

1616
await Fs.promises.mkdir(Path.dirname(cacheFilePath), { recursive: true });
1717
await Fs.promises.writeFile(cacheFilePath, data);
1818
}
1919

20-
async getUserAssociatedCachedFile(user: AbstractUser, cacheSubKey: string): Promise<Buffer | null> {
20+
async getUserAssociatedCachedFile(user: ApolloUser, cacheSubKey: string): Promise<Buffer | null> {
2121
const cacheFilePath = this.generateUserAssociatedCacheFilePath(user, cacheSubKey);
2222

2323
if (!Fs.existsSync(cacheFilePath)) {
@@ -26,13 +26,13 @@ export default class FileSystemBasedCache {
2626
return Fs.promises.readFile(cacheFilePath);
2727
}
2828

29-
private generateUserAssociatedCacheFilePath(user: AbstractUser, cacheSubKey: string): string {
29+
private generateUserAssociatedCacheFilePath(user: ApolloUser, cacheSubKey: string): string {
3030
const cacheFileName = Crypto
3131
.createHash('sha256')
3232
.update(cacheSubKey)
3333
.digest('base64');
3434

35-
return Path.join(getAppTmpDir(), 'fileSystemBasedCache', user.getId().toString(), cacheFileName);
35+
return Path.join(getAppTmpDir(), 'fileSystemBasedCache', user.id.toString(), cacheFileName);
3636
}
3737

3838
static getInstance(): FileSystemBasedCache {

0 commit comments

Comments
 (0)