@nightowne/tas-cli 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +347 -203
- package/package.json +4 -4
- package/src/cli.js +3 -1
- package/src/crypto/encryption.js +15 -2
- package/src/fuse/mount.js +137 -173
- package/src/index.js +126 -192
- package/src/share/server.js +19 -80
- package/src/sync/sync.js +23 -159
- package/src/telegram/client.js +9 -3
- package/src/utils/branding.js +1 -8
- package/src/utils/download-stream.js +87 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nightowne/tas-cli",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Telegram
|
|
3
|
+
"version": "2.4.1",
|
|
4
|
+
"description": "Turn Telegram into unlimited free cloud storage ($0/month forever) • AES-256-GCM encryption • FUSE mount • CLI-first • Zero-knowledge • No signup required",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"chalk": "^5.3.0",
|
|
48
48
|
"commander": "^12.1.0",
|
|
49
49
|
"inquirer": "^12.2.0",
|
|
50
|
-
"node-telegram-bot-api": "^
|
|
50
|
+
"node-telegram-bot-api": "^1.1.2",
|
|
51
51
|
"ora": "^8.1.1"
|
|
52
52
|
},
|
|
53
53
|
"optionalDependencies": {
|
|
@@ -56,4 +56,4 @@
|
|
|
56
56
|
"engines": {
|
|
57
57
|
"node": ">=18.0.0"
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -133,6 +133,8 @@ program
|
|
|
133
133
|
createdAt: new Date().toISOString(),
|
|
134
134
|
configVersion: 2
|
|
135
135
|
}, null, 2));
|
|
136
|
+
// Restrict config file permissions — contains encrypted token and password hash
|
|
137
|
+
try { fs.chmodSync(configPath, 0o600); } catch { /* ignore on Windows */ }
|
|
136
138
|
|
|
137
139
|
// Initialize database
|
|
138
140
|
spinner.start('Initializing local index...');
|
|
@@ -742,7 +744,7 @@ syncCmd
|
|
|
742
744
|
if (options.limit) {
|
|
743
745
|
const match = options.limit.match(/^(\d+)([kmg]?)$/i);
|
|
744
746
|
if (!match) {
|
|
745
|
-
console.error(chalk.red('Invalid limit format. Use e.g.
|
|
747
|
+
console.error(chalk.red('Invalid limit format. Use e.g. 500k, 1m'));
|
|
746
748
|
process.exit(1);
|
|
747
749
|
}
|
|
748
750
|
const val = parseInt(match[1]);
|
package/src/crypto/encryption.js
CHANGED
|
@@ -28,12 +28,18 @@ export class Encryptor {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Check password against a stored hash (supports both legacy SHA-256 and new PBKDF2 formats)
|
|
31
|
+
* Uses timing-safe comparison to prevent side-channel attacks.
|
|
31
32
|
*/
|
|
32
33
|
static verifyPasswordHash(password, storedHash) {
|
|
33
34
|
const encryptor = new Encryptor(password);
|
|
34
35
|
|
|
36
|
+
const computedHash = encryptor.getPasswordHash();
|
|
37
|
+
const computedBuf = Buffer.from(computedHash, 'utf-8');
|
|
38
|
+
const storedBuf = Buffer.from(storedHash, 'utf-8');
|
|
39
|
+
|
|
35
40
|
// Try new PBKDF2-based verification first
|
|
36
|
-
if (
|
|
41
|
+
if (computedBuf.length === storedBuf.length &&
|
|
42
|
+
crypto.timingSafeEqual(computedBuf, storedBuf)) {
|
|
37
43
|
return true;
|
|
38
44
|
}
|
|
39
45
|
|
|
@@ -41,7 +47,14 @@ export class Encryptor {
|
|
|
41
47
|
const legacyHash = crypto.createHash('sha256')
|
|
42
48
|
.update(password + 'was-verify')
|
|
43
49
|
.digest('hex');
|
|
44
|
-
|
|
50
|
+
const legacyBuf = Buffer.from(legacyHash, 'utf-8');
|
|
51
|
+
|
|
52
|
+
if (legacyBuf.length === storedBuf.length &&
|
|
53
|
+
crypto.timingSafeEqual(legacyBuf, storedBuf)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return false;
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
/**
|
package/src/fuse/mount.js
CHANGED
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import fs from 'fs';
|
|
10
|
-
import
|
|
11
|
-
import { processFile } from '../index.js';
|
|
10
|
+
import { pipeline } from 'stream/promises';
|
|
12
11
|
|
|
13
12
|
let Fuse;
|
|
14
13
|
try {
|
|
@@ -20,8 +19,11 @@ import { TelegramClient } from '../telegram/client.js';
|
|
|
20
19
|
import { Encryptor } from '../crypto/encryption.js';
|
|
21
20
|
import { Compressor } from '../utils/compression.js';
|
|
22
21
|
import { FileIndex } from '../db/index.js';
|
|
23
|
-
import { createHeader
|
|
22
|
+
import { createHeader } from '../utils/chunker.js';
|
|
23
|
+
import { createDownloadPipeline } from '../utils/download-stream.js';
|
|
24
24
|
|
|
25
|
+
// File cache for performance (avoid re-downloading)
|
|
26
|
+
const fileCache = new Map();
|
|
25
27
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
26
28
|
const CACHE_MAX_ENTRIES = 100; // Prevent unbounded memory growth
|
|
27
29
|
|
|
@@ -52,9 +54,6 @@ export class TelegramFS {
|
|
|
52
54
|
|
|
53
55
|
// Pending writes buffer
|
|
54
56
|
this.writeBuffers = new Map();
|
|
55
|
-
|
|
56
|
-
// Instance-level file cache to prevent cross-talk
|
|
57
|
-
this.fileCache = new Map();
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
async initialize() {
|
|
@@ -90,7 +89,7 @@ export class TelegramFS {
|
|
|
90
89
|
mtime: new Date(),
|
|
91
90
|
atime: new Date(),
|
|
92
91
|
ctime: new Date(),
|
|
93
|
-
size: wb.
|
|
92
|
+
size: wb.data.length,
|
|
94
93
|
mode: 0o100644, // regular file
|
|
95
94
|
uid: process.getuid?.() || 0,
|
|
96
95
|
gid: process.getgid?.() || 0
|
|
@@ -124,14 +123,9 @@ export class TelegramFS {
|
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
const files = this.db.listAll();
|
|
127
|
-
const
|
|
126
|
+
const names = files.map(f => f.filename);
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
for (const name of this.writeBuffers.keys()) {
|
|
131
|
-
nameSet.add(name);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return cb(0, [...nameSet]);
|
|
128
|
+
return cb(0, names);
|
|
135
129
|
}
|
|
136
130
|
|
|
137
131
|
/**
|
|
@@ -164,8 +158,9 @@ export class TelegramFS {
|
|
|
164
158
|
// Check write buffers first
|
|
165
159
|
const wb = this.writeBuffers.get(filename);
|
|
166
160
|
if (wb) {
|
|
167
|
-
const
|
|
168
|
-
|
|
161
|
+
const slice = wb.data.subarray(position, position + length);
|
|
162
|
+
slice.copy(buffer);
|
|
163
|
+
return cb(slice.length);
|
|
169
164
|
}
|
|
170
165
|
|
|
171
166
|
// Check cache first
|
|
@@ -190,55 +185,34 @@ export class TelegramFS {
|
|
|
190
185
|
}
|
|
191
186
|
|
|
192
187
|
/**
|
|
193
|
-
*
|
|
188
|
+
* Write to a file (buffers until release)
|
|
194
189
|
*/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return this.writeBuffers.get(filename);
|
|
198
|
-
}
|
|
190
|
+
write(filepath, fd, buffer, length, position, cb) {
|
|
191
|
+
const filename = path.basename(filepath);
|
|
199
192
|
|
|
200
|
-
|
|
201
|
-
if (!
|
|
202
|
-
|
|
193
|
+
// Get or create write buffer
|
|
194
|
+
if (!this.writeBuffers.has(filename)) {
|
|
195
|
+
this.writeBuffers.set(filename, {
|
|
196
|
+
data: Buffer.alloc(0),
|
|
197
|
+
modified: true
|
|
198
|
+
});
|
|
203
199
|
}
|
|
204
200
|
|
|
205
|
-
const
|
|
206
|
-
const fdDisk = fs.openSync(tmpPath, 'w+');
|
|
201
|
+
const wb = this.writeBuffers.get(filename);
|
|
207
202
|
|
|
208
|
-
//
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
size = content.length;
|
|
203
|
+
// Expand buffer if needed
|
|
204
|
+
const newSize = Math.max(wb.data.length, position + length);
|
|
205
|
+
if (newSize > wb.data.length) {
|
|
206
|
+
const newBuf = Buffer.alloc(newSize);
|
|
207
|
+
wb.data.copy(newBuf);
|
|
208
|
+
wb.data = newBuf;
|
|
215
209
|
}
|
|
216
210
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
size,
|
|
221
|
-
modified: true
|
|
222
|
-
};
|
|
223
|
-
this.writeBuffers.set(filename, wb);
|
|
224
|
-
return wb;
|
|
225
|
-
}
|
|
211
|
+
// Copy incoming data
|
|
212
|
+
buffer.copy(wb.data, position, 0, length);
|
|
213
|
+
wb.modified = true;
|
|
226
214
|
|
|
227
|
-
|
|
228
|
-
* Write to a file (buffers until release)
|
|
229
|
-
*/
|
|
230
|
-
write(filepath, fd, buffer, length, position, cb) {
|
|
231
|
-
const filename = path.basename(filepath);
|
|
232
|
-
try {
|
|
233
|
-
const wb = this.getOrCreateWriteBuffer(filename);
|
|
234
|
-
fs.writeSync(wb.fd, buffer, 0, length, position);
|
|
235
|
-
wb.size = Math.max(wb.size, position + length);
|
|
236
|
-
wb.modified = true;
|
|
237
|
-
return cb(length);
|
|
238
|
-
} catch (err) {
|
|
239
|
-
console.error('[FUSE] Write error:', err.message);
|
|
240
|
-
return cb(Fuse.EIO);
|
|
241
|
-
}
|
|
215
|
+
return cb(length);
|
|
242
216
|
}
|
|
243
217
|
|
|
244
218
|
/**
|
|
@@ -249,19 +223,9 @@ export class TelegramFS {
|
|
|
249
223
|
|
|
250
224
|
console.log(`[FUSE] Creating file: ${filename}`);
|
|
251
225
|
|
|
252
|
-
// Initialize empty
|
|
253
|
-
const fuseTmpDir = path.join(this.dataDir, 'fuse-tmp');
|
|
254
|
-
if (!fs.existsSync(fuseTmpDir)) {
|
|
255
|
-
fs.mkdirSync(fuseTmpDir, { recursive: true });
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const tmpPath = path.join(fuseTmpDir, crypto.randomBytes(16).toString('hex'));
|
|
259
|
-
const fdDisk = fs.openSync(tmpPath, 'w+');
|
|
260
|
-
|
|
226
|
+
// Initialize empty write buffer
|
|
261
227
|
this.writeBuffers.set(filename, {
|
|
262
|
-
|
|
263
|
-
fd: fdDisk,
|
|
264
|
-
size: 0,
|
|
228
|
+
data: Buffer.alloc(0),
|
|
265
229
|
modified: true,
|
|
266
230
|
isNew: true
|
|
267
231
|
});
|
|
@@ -283,26 +247,15 @@ export class TelegramFS {
|
|
|
283
247
|
const filename = path.basename(filepath);
|
|
284
248
|
|
|
285
249
|
const wb = this.writeBuffers.get(filename);
|
|
286
|
-
if (!wb) {
|
|
287
|
-
return cb(0);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (!wb.modified) {
|
|
291
|
-
try { fs.closeSync(wb.fd); } catch (e) {}
|
|
292
|
-
try { fs.unlinkSync(wb.tmpPath); } catch (e) {}
|
|
293
|
-
this.writeBuffers.delete(filename);
|
|
250
|
+
if (!wb || !wb.modified) {
|
|
294
251
|
return cb(0);
|
|
295
252
|
}
|
|
296
253
|
|
|
297
254
|
try {
|
|
298
|
-
//
|
|
299
|
-
|
|
255
|
+
// Upload to Telegram
|
|
256
|
+
await this.uploadFile(filename, wb.data);
|
|
300
257
|
|
|
301
|
-
//
|
|
302
|
-
await this.uploadFile(filename, wb.tmpPath);
|
|
303
|
-
|
|
304
|
-
// Clean up temp file
|
|
305
|
-
try { fs.unlinkSync(wb.tmpPath); } catch (e) {}
|
|
258
|
+
// Clear write buffer
|
|
306
259
|
this.writeBuffers.delete(filename);
|
|
307
260
|
|
|
308
261
|
// Invalidate cache
|
|
@@ -310,9 +263,7 @@ export class TelegramFS {
|
|
|
310
263
|
|
|
311
264
|
return cb(0);
|
|
312
265
|
} catch (err) {
|
|
313
|
-
console.error('
|
|
314
|
-
try { fs.unlinkSync(wb.tmpPath); } catch (e) {}
|
|
315
|
-
this.writeBuffers.delete(filename);
|
|
266
|
+
console.error('Release error:', err.message);
|
|
316
267
|
return cb(Fuse.EIO);
|
|
317
268
|
}
|
|
318
269
|
}
|
|
@@ -329,7 +280,7 @@ export class TelegramFS {
|
|
|
329
280
|
}
|
|
330
281
|
|
|
331
282
|
try {
|
|
332
|
-
// Delete from Telegram
|
|
283
|
+
// Delete from Telegram (optional - could just remove from index)
|
|
333
284
|
const chunks = this.db.getChunks(file.id);
|
|
334
285
|
for (const chunk of chunks) {
|
|
335
286
|
await this.client.deleteMessage(chunk.message_id);
|
|
@@ -343,7 +294,7 @@ export class TelegramFS {
|
|
|
343
294
|
|
|
344
295
|
return cb(0);
|
|
345
296
|
} catch (err) {
|
|
346
|
-
console.error('
|
|
297
|
+
console.error('Unlink error:', err.message);
|
|
347
298
|
return cb(Fuse.EIO);
|
|
348
299
|
}
|
|
349
300
|
}
|
|
@@ -365,10 +316,10 @@ export class TelegramFS {
|
|
|
365
316
|
.run(newName, file.id);
|
|
366
317
|
|
|
367
318
|
// Update cache key
|
|
368
|
-
const cached =
|
|
319
|
+
const cached = fileCache.get(oldName);
|
|
369
320
|
if (cached) {
|
|
370
|
-
|
|
371
|
-
|
|
321
|
+
fileCache.delete(oldName);
|
|
322
|
+
fileCache.set(newName, cached);
|
|
372
323
|
}
|
|
373
324
|
|
|
374
325
|
return cb(0);
|
|
@@ -379,16 +330,35 @@ export class TelegramFS {
|
|
|
379
330
|
*/
|
|
380
331
|
truncate(filepath, size, cb) {
|
|
381
332
|
const filename = path.basename(filepath);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
333
|
+
|
|
334
|
+
// Get or load into write buffer
|
|
335
|
+
if (!this.writeBuffers.has(filename)) {
|
|
336
|
+
const cachedPath = this.getCached(filename);
|
|
337
|
+
if (cachedPath) {
|
|
338
|
+
this.writeBuffers.set(filename, {
|
|
339
|
+
data: fs.readFileSync(cachedPath), // Note: RAM buffer here could be big, but it's okay for truncate/writes right now
|
|
340
|
+
modified: true
|
|
341
|
+
});
|
|
342
|
+
} else {
|
|
343
|
+
this.writeBuffers.set(filename, {
|
|
344
|
+
data: Buffer.alloc(0),
|
|
345
|
+
modified: true
|
|
346
|
+
});
|
|
347
|
+
}
|
|
391
348
|
}
|
|
349
|
+
|
|
350
|
+
const wb = this.writeBuffers.get(filename);
|
|
351
|
+
|
|
352
|
+
if (size < wb.data.length) {
|
|
353
|
+
wb.data = wb.data.subarray(0, size);
|
|
354
|
+
} else if (size > wb.data.length) {
|
|
355
|
+
const newBuf = Buffer.alloc(size);
|
|
356
|
+
wb.data.copy(newBuf);
|
|
357
|
+
wb.data = newBuf;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
wb.modified = true;
|
|
361
|
+
return cb(0);
|
|
392
362
|
}
|
|
393
363
|
|
|
394
364
|
// ============== Helper Methods ==============
|
|
@@ -413,56 +383,18 @@ export class TelegramFS {
|
|
|
413
383
|
}
|
|
414
384
|
|
|
415
385
|
const chunks = this.db.getChunks(file.id);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const header = parseHeader(firstChunkData);
|
|
423
|
-
let wasCompressed = header.compressed;
|
|
424
|
-
|
|
425
|
-
const decryptStream = this.encryptor.getDecryptStream();
|
|
426
|
-
const decompressStream = this.compressor.getDecompressStream(wasCompressed);
|
|
427
|
-
|
|
428
|
-
const { Readable } = await import('stream');
|
|
429
|
-
const { pipeline } = await import('stream/promises');
|
|
430
|
-
|
|
431
|
-
const self = this;
|
|
432
|
-
let currentChunkIndex = 0;
|
|
433
|
-
let preloadedFirstChunk = firstChunkData;
|
|
434
|
-
|
|
435
|
-
const downloadStream = new Readable({
|
|
436
|
-
async read() {
|
|
437
|
-
try {
|
|
438
|
-
if (currentChunkIndex >= chunks.length) {
|
|
439
|
-
this.push(null);
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const chunk = chunks[currentChunkIndex];
|
|
444
|
-
let data;
|
|
445
|
-
if (currentChunkIndex === 0 && preloadedFirstChunk) {
|
|
446
|
-
data = preloadedFirstChunk;
|
|
447
|
-
preloadedFirstChunk = null;
|
|
448
|
-
} else {
|
|
449
|
-
data = await self.client.downloadFile(chunk.file_telegram_id);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const payload = data.subarray(HEADER_SIZE);
|
|
453
|
-
this.push(payload);
|
|
454
|
-
currentChunkIndex++;
|
|
455
|
-
} catch (err) {
|
|
456
|
-
this.destroy(err);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
386
|
+
|
|
387
|
+
const { readable } = await createDownloadPipeline({
|
|
388
|
+
client: this.client,
|
|
389
|
+
chunks,
|
|
390
|
+
encryptor: this.encryptor,
|
|
391
|
+
compressor: this.compressor
|
|
459
392
|
});
|
|
460
393
|
|
|
461
394
|
const tmpOutputPath = outputPath + '.tmp';
|
|
462
395
|
const writeStream = fs.createWriteStream(tmpOutputPath);
|
|
463
396
|
|
|
464
|
-
|
|
465
|
-
await pipeline(downloadStream, decryptStream, decompressStream, writeStream);
|
|
397
|
+
await pipeline(readable, writeStream);
|
|
466
398
|
|
|
467
399
|
// Rename to final atomic path
|
|
468
400
|
fs.renameSync(tmpOutputPath, outputPath);
|
|
@@ -470,7 +402,10 @@ export class TelegramFS {
|
|
|
470
402
|
return outputPath;
|
|
471
403
|
}
|
|
472
404
|
|
|
473
|
-
async uploadFile(filename,
|
|
405
|
+
async uploadFile(filename, data) {
|
|
406
|
+
const { hashData } = await import('../crypto/encryption.js');
|
|
407
|
+
const hash = hashData(data);
|
|
408
|
+
|
|
474
409
|
// Check if already exists by name
|
|
475
410
|
const existingByName = this.db.findByName(filename);
|
|
476
411
|
if (existingByName) {
|
|
@@ -482,17 +417,57 @@ export class TelegramFS {
|
|
|
482
417
|
this.db.delete(existingByName.id);
|
|
483
418
|
}
|
|
484
419
|
|
|
485
|
-
//
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
420
|
+
// Check if already exists by hash (same content, different name)
|
|
421
|
+
const existingByHash = this.db.findByHash(hash);
|
|
422
|
+
if (existingByHash) {
|
|
423
|
+
// Same content already exists, just skip
|
|
424
|
+
console.log(`[FUSE] File with same content already exists as ${existingByHash.filename}`);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Compress
|
|
429
|
+
const { data: compressedData, compressed } = await this.compressor.compress(data, filename);
|
|
430
|
+
|
|
431
|
+
// Encrypt
|
|
432
|
+
const encryptedData = this.encryptor.encrypt(compressedData);
|
|
433
|
+
|
|
434
|
+
// Create temp file with header
|
|
435
|
+
const tempDir = process.env.TAS_TMP_DIR || path.join(this.dataDir, 'tmp');
|
|
436
|
+
if (!fs.existsSync(tempDir)) {
|
|
437
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const flags = compressed ? 1 : 0;
|
|
441
|
+
const header = createHeader(filename, data.length, 0, 1, flags);
|
|
442
|
+
const fileData = Buffer.concat([header, encryptedData]);
|
|
443
|
+
|
|
444
|
+
const tempPath = path.join(tempDir, `${hash.substring(0, 12)}.tas`);
|
|
445
|
+
fs.writeFileSync(tempPath, fileData);
|
|
446
|
+
|
|
447
|
+
// Upload to Telegram
|
|
448
|
+
const result = await this.client.sendFile(tempPath, `📦 ${filename}`);
|
|
449
|
+
|
|
450
|
+
// Add to index
|
|
451
|
+
const fileId = this.db.addFile({
|
|
452
|
+
filename,
|
|
453
|
+
hash,
|
|
454
|
+
originalSize: data.length,
|
|
455
|
+
storedSize: encryptedData.length,
|
|
456
|
+
chunks: 1,
|
|
457
|
+
compressed
|
|
491
458
|
});
|
|
459
|
+
|
|
460
|
+
this.db.addChunk(fileId, 0, result.messageId.toString(), fileData.length);
|
|
461
|
+
this.db.db.prepare('UPDATE chunks SET file_telegram_id = ? WHERE file_id = ? AND chunk_index = ?')
|
|
462
|
+
.run(result.fileId, fileId, 0);
|
|
463
|
+
|
|
464
|
+
// Cleanup
|
|
465
|
+
fs.unlinkSync(tempPath);
|
|
466
|
+
try { fs.rmdirSync(tempDir); } catch (e) { }
|
|
492
467
|
}
|
|
493
468
|
|
|
494
469
|
getCached(filename) {
|
|
495
|
-
const entry =
|
|
470
|
+
const entry = fileCache.get(filename);
|
|
496
471
|
if (!entry) return null;
|
|
497
472
|
|
|
498
473
|
if (Date.now() - entry.timestamp > CACHE_TTL) {
|
|
@@ -500,7 +475,7 @@ export class TelegramFS {
|
|
|
500
475
|
try {
|
|
501
476
|
if (fs.existsSync(entry.path)) fs.unlinkSync(entry.path);
|
|
502
477
|
} catch (e) { }
|
|
503
|
-
|
|
478
|
+
fileCache.delete(filename);
|
|
504
479
|
return null;
|
|
505
480
|
}
|
|
506
481
|
|
|
@@ -511,35 +486,35 @@ export class TelegramFS {
|
|
|
511
486
|
|
|
512
487
|
setCache(filename, cachePath) {
|
|
513
488
|
// Evict oldest entry if cache is full
|
|
514
|
-
if (
|
|
489
|
+
if (fileCache.size >= CACHE_MAX_ENTRIES) {
|
|
515
490
|
let oldestKey = null;
|
|
516
491
|
let oldestTime = Infinity;
|
|
517
|
-
for (const [key, entry] of
|
|
492
|
+
for (const [key, entry] of fileCache) {
|
|
518
493
|
if (entry.timestamp < oldestTime) {
|
|
519
494
|
oldestTime = entry.timestamp;
|
|
520
495
|
oldestKey = key;
|
|
521
496
|
}
|
|
522
497
|
}
|
|
523
498
|
if (oldestKey) {
|
|
524
|
-
const evicted =
|
|
499
|
+
const evicted = fileCache.get(oldestKey);
|
|
525
500
|
try { if (fs.existsSync(evicted.path)) fs.unlinkSync(evicted.path); } catch (e) { }
|
|
526
|
-
|
|
501
|
+
fileCache.delete(oldestKey);
|
|
527
502
|
}
|
|
528
503
|
}
|
|
529
504
|
|
|
530
|
-
|
|
505
|
+
fileCache.set(filename, {
|
|
531
506
|
path: cachePath,
|
|
532
507
|
timestamp: Date.now()
|
|
533
508
|
});
|
|
534
509
|
}
|
|
535
510
|
|
|
536
511
|
invalidateCache(filename) {
|
|
537
|
-
const entry =
|
|
512
|
+
const entry = fileCache.get(filename);
|
|
538
513
|
if (entry) {
|
|
539
514
|
try {
|
|
540
515
|
if (fs.existsSync(entry.path)) fs.unlinkSync(entry.path);
|
|
541
516
|
} catch (e) { }
|
|
542
|
-
|
|
517
|
+
fileCache.delete(filename);
|
|
543
518
|
}
|
|
544
519
|
}
|
|
545
520
|
|
|
@@ -552,17 +527,6 @@ export class TelegramFS {
|
|
|
552
527
|
fs.mkdirSync(this.mountPoint, { recursive: true });
|
|
553
528
|
}
|
|
554
529
|
|
|
555
|
-
// Clean up any stray temp files from previous sessions
|
|
556
|
-
const fuseTmpDir = path.join(this.dataDir, 'fuse-tmp');
|
|
557
|
-
if (fs.existsSync(fuseTmpDir)) {
|
|
558
|
-
try {
|
|
559
|
-
const files = fs.readdirSync(fuseTmpDir);
|
|
560
|
-
for (const file of files) {
|
|
561
|
-
fs.unlinkSync(path.join(fuseTmpDir, file));
|
|
562
|
-
}
|
|
563
|
-
} catch (e) {}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
530
|
const ops = {
|
|
567
531
|
getattr: this.getattr.bind(this),
|
|
568
532
|
readdir: this.readdir.bind(this),
|