Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,8 @@ clamAV/**
CLAUDE.md

enable-sso.sh

# Local Copilot customization files
.github/copilot-instructions.md
.github/instructions/contributing.instructions.md
.github/instructions/testing.instructions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Environment } from '@internxt/inxt-js';
import { ContainerBuilder } from 'diod';
import { app } from 'electron';
import path from 'path';
import { StorageClearer } from '../../../../context/storage/StorageFiles/application/delete/StorageClearer';
import { StorageFileDeleter } from '../../../../context/storage/StorageFiles/application/delete/StorageFileDeleter';
import { MakeStorageFileAvaliableOffline } from '../../../../context/storage/StorageFiles/application/offline/MakeStorageFileAvaliableOffline';
Expand All @@ -13,18 +11,16 @@ import { EnvironmentFileDownloaderHandlerFactory } from '../../../../context/sto
import { TypeOrmAndNodeFsStorageFilesRepository } from '../../../../context/storage/StorageFiles/infrastructure/persistance/repository/typeorm/TypeOrmAndNodeFsStorageFilesRepository';
import { TypeOrmStorageFilesDataSourceFactory } from '../../../../context/storage/StorageFiles/infrastructure/persistance/repository/typeorm/TypeOrmStorageFilesDataSourceFactory';
import { DependencyInjectionUserProvider } from '../../../shared/dependency-injection/DependencyInjectionUserProvider';
import { PATHS } from '../../../../core/electron/paths';

export async function registerStorageFilesServices(builder: ContainerBuilder): Promise<void> {
// Infra

const appData = app.getPath('appData');
const local = path.join(appData, 'internxt-drive', 'downloaded');

const user = DependencyInjectionUserProvider.get();

const dataSource = await TypeOrmStorageFilesDataSourceFactory.create();

const repo = new TypeOrmAndNodeFsStorageFilesRepository(local, dataSource);
const repo = new TypeOrmAndNodeFsStorageFilesRepository(PATHS.DOWNLOADED, dataSource);
await repo.init();

builder.register(StorageFilesRepository).useInstance(repo);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { ContainerBuilder } from 'diod';
import path from 'path';
import { AbsolutePathToRelativeConverter } from '../../../../context/virtual-drive/shared/application/AbsolutePathToRelativeConverter';
import { RelativePathToAbsoluteConverter } from '../../../../context/virtual-drive/shared/application/RelativePathToAbsoluteConverter';
import { FuseAppDataLocalFileContentsDirectoryProvider } from '../../../../context/virtual-drive/shared/infrastructure/LocalFileContentsDirectoryProviders/FuseAppDataLocalFileContentsDirectoryProvider';
import { PATHS } from '../../../../core/electron/paths';

export async function registerVirtualDriveSharedServices(builder: ContainerBuilder): Promise<void> {
const localFileContentsDirectoryProvider = new FuseAppDataLocalFileContentsDirectoryProvider();
const downloaded = PATHS.DOWNLOADED;

const dir = await localFileContentsDirectoryProvider.provide();

const base = path.join(dir, 'downloaded');

builder.register(RelativePathToAbsoluteConverter).useFactory(() => new RelativePathToAbsoluteConverter(base));
builder.register(AbsolutePathToRelativeConverter).useFactory(() => new AbsolutePathToRelativeConverter(base));
builder.register(RelativePathToAbsoluteConverter).useFactory(() => new RelativePathToAbsoluteConverter(downloaded));
builder.register(AbsolutePathToRelativeConverter).useFactory(() => new AbsolutePathToRelativeConverter(downloaded));
}
1 change: 0 additions & 1 deletion src/apps/main/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const schema: Schema<AppStore> = {
backupInterval: { type: 'number' },
lastBackup: { type: 'number' },
syncRoot: { type: 'string' },
logEnginePath: { type: 'string' },
lastSavedListing: { type: 'string' },
lastSync: { type: 'number' },

Expand Down
8 changes: 3 additions & 5 deletions src/apps/main/database/data-source.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { app } from 'electron';
import eventBus from '../event-bus';
import { DataSource } from 'typeorm';
import { DriveFile } from './entities/DriveFile';
import { DriveFolder } from './entities/DriveFolder';
import { ScannedItem } from './entities/ScannedItem';
import { logger } from '@internxt/drive-desktop-core/build/backend';

const dbPath = app.getPath('appData') + '/internxt-drive/internxt_desktop.db';
import { PATHS } from '../../../core/electron/paths';

export const AppDataSource = new DataSource({
type: 'better-sqlite3',
database: dbPath,
database: PATHS.DATABASE,
logging: false,
synchronize: true,
entities: [DriveFile, DriveFolder, ScannedItem],
});

logger.debug({ msg: `Using database file at ${dbPath}` });
logger.debug({ msg: `Using database file at ${PATHS.DATABASE}` });

eventBus.on('USER_LOGGED_OUT', () => {
AppDataSource.dropDatabase().catch((error) => {
Expand Down
25 changes: 4 additions & 21 deletions src/apps/main/virtual-root-folder/service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { app, dialog, shell } from 'electron';
import { dialog, shell } from 'electron';
import fs from 'fs/promises';
import path from 'path';
import path from 'node:path';
import configStore from '../config';
import eventBus from '../event-bus';
import { exec } from 'child_process';
import { ensureFolderExists } from '../../shared/fs/ensure-folder-exists';
import { PATHS } from '../../../core/electron/paths';

const ROOT_FOLDER_NAME = 'Internxt Drive';
const HOME_FOLDER_PATH = app.getPath('home');

const VIRTUAL_DRIVE_FOLDER = path.join(HOME_FOLDER_PATH, ROOT_FOLDER_NAME);
const VIRTUAL_DRIVE_FOLDER = PATHS.ROOT_DRIVE_FOLDER;

async function existsFolder(pathname: string): Promise<boolean> {
try {
Expand Down Expand Up @@ -40,8 +38,6 @@

function setSyncRoot(pathname: string): void {
const pathNameWithSepInTheEnd = pathname[pathname.length - 1] === path.sep ? pathname : pathname + path.sep;
const logEnginePath = path.join(app.getPath('appData'), 'internxt-drive', 'logs', 'node-win.txt');
configStore.set('logEnginePath', logEnginePath);
configStore.set('syncRoot', pathNameWithSepInTheEnd);
configStore.set('lastSavedListing', '');
}
Expand All @@ -59,20 +55,7 @@

export async function setupRootFolder(n = 0): Promise<void> {
setSyncRoot(VIRTUAL_DRIVE_FOLDER);
return;

Check warning on line 58 in src/apps/main/virtual-root-folder/service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this redundant jump.

See more on https://sonarcloud.io/project/issues?id=internxt_drive-desktop-linux&issues=AZ1ln7siKxecox-xcQ4X&open=AZ1ln7siKxecox-xcQ4X&pullRequest=292
const folderName = ROOT_FOLDER_NAME;

const rootFolderName = folderName + (n ? ` (${n})` : '');
const rootFolderPath = path.join(HOME_FOLDER_PATH, rootFolderName);

const notExistsOrIsEmpty = !(await existsFolder(rootFolderPath)) || (await isEmptyFolder(rootFolderPath));

if (notExistsOrIsEmpty) {
await fs.mkdir(rootFolderPath, { recursive: true });
setSyncRoot(rootFolderPath);
} else {
return setupRootFolder(n + 1);
}
}

export async function chooseSyncRootWithDialog(): Promise<string | null> {
Expand Down
60 changes: 47 additions & 13 deletions src/backend/features/fuse/on-read/stream-file-to-disk.test.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,111 @@
import fs from 'node:fs';
import { PassThrough } from 'node:stream';
import * as ensureFolderExistsModule from '../../../../apps/shared/fs/ensure-folder-exists';
import { streamFileToDisk } from './stream-file-to-disk';
import { deepMocked } from '../../../../../tests/vitest/utils.helper';
import { call, calls, partialSpyOn } from '../../../../../tests/vitest/utils.helper';

vi.mock(import('node:fs'));
describe('stream-file-to-disk', () => {
const createWriteStreamMock = partialSpyOn(fs, 'createWriteStream');
const ensureFolderExistsMock = partialSpyOn(ensureFolderExistsModule, 'ensureFolderExists');

const fsMock = deepMocked(fs);

describe('streamFileToDisk', () => {
let fakeWriteStream: PassThrough & { bytesWritten: number };

beforeEach(() => {
fakeWriteStream = Object.assign(new PassThrough(), { bytesWritten: 0 });
fsMock.createWriteStream.mockReturnValue(fakeWriteStream as any);
createWriteStreamMock.mockReturnValue(fakeWriteStream as unknown as fs.WriteStream);
});

it('should create a write stream at the given file path', () => {
it('should ensure the destination folder exists before creating the stream', () => {
// Given
const readable = new PassThrough();

streamFileToDisk(readable, '/tmp/file', vi.fn());
// When
streamFileToDisk(readable, '/home/dev/.config/internxt/downloaded/file-id', vi.fn());

expect(fsMock.createWriteStream).toHaveBeenCalledWith('/tmp/file');
// Then
call(ensureFolderExistsMock).toBe('/home/dev/.config/internxt/downloaded');
call(createWriteStreamMock).toBe('/home/dev/.config/internxt/downloaded/file-id');
});

it('should pipe the readable into the write stream', () => {
// Given
const readable = new PassThrough();
const pipeSpy = vi.spyOn(readable, 'pipe');
const pipeSpy = partialSpyOn(readable, 'pipe', false);

// When
streamFileToDisk(readable, '/tmp/file', vi.fn());

expect(pipeSpy).toHaveBeenCalledWith(fakeWriteStream);
// Then
call(pipeSpy).toBe(fakeWriteStream);
});

it('should call onProgress with bytesWritten when data is received', () => {
// Given
const readable = new PassThrough();
const onProgress = vi.fn();
fakeWriteStream.bytesWritten = 100;

// When
streamFileToDisk(readable, '/tmp/file', onProgress);
readable.emit('data', Buffer.alloc(100));

expect(onProgress).toHaveBeenCalledWith(100);
// Then
call(onProgress).toBe(100);
});

it('should call onProgress with bytesWritten on drain', () => {
// Given
const readable = new PassThrough();
const onProgress = vi.fn();
fakeWriteStream.bytesWritten = 200;

// When
streamFileToDisk(readable, '/tmp/file', onProgress);
fakeWriteStream.emit('drain');

expect(onProgress).toHaveBeenCalledWith(200);
// Then
call(onProgress).toBe(200);
});

it('should return the write stream', () => {
// Given
const readable = new PassThrough();

// When
const result = streamFileToDisk(readable, '/tmp/file', vi.fn());

// Then
expect(result.writeStream).toBe(fakeWriteStream);
});

it('should return getBytesWritten that tracks progress', () => {
// Given
const readable = new PassThrough();

// When
const result = streamFileToDisk(readable, '/tmp/file', vi.fn());

// Then
expect(result.getBytesWritten()).toBe(0);

// Given
fakeWriteStream.bytesWritten = 500;

// When
readable.emit('data', Buffer.alloc(500));

// Then
expect(result.getBytesWritten()).toBe(500);
});

it('should create the write stream once', () => {
// Given
const readable = new PassThrough();

// When
streamFileToDisk(readable, '/tmp/file', vi.fn());

// Then
calls(createWriteStreamMock).toHaveLength(1);
});
});
4 changes: 4 additions & 0 deletions src/backend/features/fuse/on-read/stream-file-to-disk.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import fs from 'node:fs';
import { dirname } from 'node:path';
import { type Readable } from 'node:stream';
import { ensureFolderExists } from '../../../../apps/shared/fs/ensure-folder-exists';

export function streamFileToDisk(readable: Readable, filePath: string, onProgress: (bytesWritten: number) => void) {
let bytesWritten = 0;

ensureFolderExists(dirname(filePath));

const writeStream = fs.createWriteStream(filePath);

readable.pipe(writeStream);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { DataSource } from 'typeorm';
import { TypeOrmStorageFile } from './entities/TypeOrmStorageFile';
import { app } from 'electron';
import { PATHS } from '../../../../../../../core/electron/paths';

export class TypeOrmStorageFilesDataSourceFactory {
static async create(): Promise<DataSource> {
const dbPath = app.getPath('appData') + '/internxt-drive/internxt_desktop.db';

const s = new DataSource({
type: 'better-sqlite3',
database: dbPath,
database: PATHS.DATABASE,
logging: false,
synchronize: true,
entities: [TypeOrmStorageFile],
Expand Down

This file was deleted.

This file was deleted.

9 changes: 7 additions & 2 deletions src/core/electron/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import os from 'node:os';

const HOME_FOLDER_PATH = app.getPath('home');
const APP_DATA_PATH = app.getPath('appData');
const INTERNXT = join(APP_DATA_PATH, 'internxt-drive');
const LOGS = join(HOME_FOLDER_PATH, '.config', 'internxt', 'logs');
const INTERNXT = join(APP_DATA_PATH, 'internxt');
const INTERNXT_DRIVE = join(APP_DATA_PATH, 'internxt-drive');
const LOGS = join(INTERNXT, 'logs');
const DATABASE = join(INTERNXT_DRIVE, 'internxt_desktop.db');
const ROOT_DRIVE_FOLDER = join(HOME_FOLDER_PATH, 'Internxt Drive');
const THUMBNAILS_FOLDER = path.join(os.homedir(), '.cache', 'thumbnails');
const TEMPORAL_FOLDER = app.getPath('temp');
const INTERNXT_DRIVE_TMP = path.join(TEMPORAL_FOLDER, 'internxt-drive-tmp');
Expand All @@ -15,8 +18,10 @@ export const PATHS = {
HOME_FOLDER_PATH,
INTERNXT,
LOGS,
DATABASE,
THUMBNAILS_FOLDER,
TEMPORAL_FOLDER,
INTERNXT_DRIVE_TMP,
ROOT_DRIVE_FOLDER,
DOWNLOADED,
};
1 change: 0 additions & 1 deletion src/core/electron/store/app-store.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export type AppStore = {
backupInterval: number;
lastBackup: number;
syncRoot: string;
logEnginePath: string;
lastSavedListing: string;
lastSync: number;

Expand Down
1 change: 0 additions & 1 deletion src/core/electron/store/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export const defaults: AppStore = {
backupInterval: 86_400_000, // 24h
lastBackup: -1,
syncRoot: '',
logEnginePath: '',
lastSavedListing: '',
lastSync: -1,

Expand Down
Loading