Skip to content

Commit

Permalink
fix(api): cached files are duplicated (same content stored twice)
Browse files Browse the repository at this point in the history
  • Loading branch information
JiPaix committed May 6, 2023
1 parent f411047 commit fd96e8b
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 2 deletions.
39 changes: 38 additions & 1 deletion packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import history from 'connect-history-api-fallback';
import cors from 'cors';
import express from 'express';
import morgan from 'morgan';
import { join } from 'path';
import { join, resolve } from 'path';
import { env } from 'process';

export default function useFork(settings: ForkEnv = env):Promise<{ client: client, fork:Fork }> {
Expand Down Expand Up @@ -65,6 +65,26 @@ export default function useFork(settings: ForkEnv = env):Promise<{ client: clien
});
});

// serve the files
app.get('/cache/:folderName/:fileName', (req, res, next) => {
const options = {
root: resolve(fileServer.cacheFolder, req.params.folderName),
dotfiles: 'deny',
headers: {
'x-timestamp': Date.now(),
'x-sent': true,
},
};
const fileName = req.params.fileName;
res.sendFile(fileName, options, (err) => {
if (err) {
next(err);
} else {
fileServer.renew(fileName);
}
});
});

// force authentication for file requests
app.use('/files', (req, res, next) => {
const b64auth = (req.headers.authorization || '').split(' ')[1] || '';
Expand All @@ -82,6 +102,23 @@ export default function useFork(settings: ForkEnv = env):Promise<{ client: clien
}
});

// force authentication for cache requests
app.use('/cache', (req, res, next) => {
const b64auth = (req.headers.authorization || '').split(' ')[1] || '';
const strauth = Buffer.from(b64auth, 'base64').toString();
const splitIndex = strauth.indexOf(':');
const login = strauth.substring(0, splitIndex);
const password = strauth.substring(splitIndex + 1);
if(login !== env.LOGIN || password !== env.PASSWORD) {
// Access denied...
res.set('WWW-Authenticate', 'Basic realm="401"'); // change this
res.status(401).send('Authentication required.'); // custom message
} else {
// Access granted...
next();
}
});

// serve the view if any
if(env.VIEW) {
app.use(express.static(env.VIEW));
Expand Down
33 changes: 32 additions & 1 deletion packages/api/src/utils/fileserv.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'fs';
import { join, resolve } from 'path';
import { basename, dirname, join, resolve } from 'path';
import { env } from 'process';
import sanitize from 'sanitize-filename';


function* getFiles(dir: string):Generator<string, void, unknown> {
const dirents = readdirSync(dir, { withFileTypes: true });
for (const dirent of dirents.filter(f => f.name !== 'fileserver')) {
const res = resolve(dir, dirent.name);
if (dirent.isDirectory()) {
yield* getFiles(res);
} else {
yield res;
}
}
}


export class FileServer {
static #instance: FileServer;
folder: string;
cacheFolder: string;
/** default file's lifetime in seconds */
#defaultLifeTime = 60*60*24;
#timeouts: {filename: string, timeout: NodeJS.Timeout}[];
Expand All @@ -15,6 +31,7 @@ export class FileServer {
constructor(folder:string) {
if(!env.USER_DATA) throw new Error('USER_DATA not set');
this.folder = resolve(env.USER_DATA, '.cache', folder);
this.cacheFolder = resolve(env.USER_DATA, '.cache');
this.#timeouts = [];
this.setup();
this.empty();
Expand Down Expand Up @@ -63,6 +80,10 @@ export class FileServer {
return resolve(this.folder, sanitize(filename));
}

#resolveFilesFromCache() {
return getFiles(this.cacheFolder);
}

#resetFile(filename: string, lifetime: number) {
const path = this.#resolveFile(filename);
const fileExist = existsSync(path);
Expand Down Expand Up @@ -101,13 +122,23 @@ export class FileServer {
}
}

#findFromCache(filename: string) {
const files = this.#resolveFilesFromCache();
for(const f of files) {
if(f.includes(sanitize(filename))) return f;
}
}

/**
*
* @param data the data to serv
* @param filename its filename
* @param lifetime how lang is the file available (in seconds)
*/
serv (data: Buffer, filename:string, lifetime = this.#defaultLifeTime) {
const cached = this.#findFromCache(filename);
if(cached) return `/cache/${basename(dirname(cached))}/${filename}`;

const exist = this.#resetFile(filename, lifetime);
if(exist) return `/files/${filename}`;

Expand Down

0 comments on commit fd96e8b

Please sign in to comment.