Skip to content

Commit

Permalink
Merge branch 'dev' into feature/profile_editing
Browse files Browse the repository at this point in the history
  • Loading branch information
undyingwraith committed Jan 14, 2025
2 parents 72873e2 + 95f39c8 commit 72a716e
Show file tree
Hide file tree
Showing 82 changed files with 5,153 additions and 2,864 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ run-name: Build triggered by @${{ github.actor }}
on: [push]
jobs:
build:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -13,3 +16,5 @@ jobs:
run: yarn --frozen-lockfile --check-cache
- name: Run build
run: yarn build
- name: Run tests
run: yarn test
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"arcanis.vscode-zipfs",
"github.vscode-github-actions",
"dsznajder.es7-react-js-snippets",
"ecmel.vscode-html-css"
"ecmel.vscode-html-css",
"redhat.vscode-yaml"
]
}
894 changes: 0 additions & 894 deletions .yarn/releases/yarn-4.3.1.cjs

This file was deleted.

934 changes: 934 additions & 0 deletions .yarn/releases/yarn-4.5.3.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ packageExtensions:
dependencies:
undici: "*"

yarnPath: .yarn/releases/yarn-4.3.1.cjs
yarnPath: .yarn/releases/yarn-4.5.3.cjs
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ yarn workspace watch
yarn workspace ipmc-desktop run dev
```

*NOTE: for first time install you might need to run a build first*
```bash
yarn build
```

## Packages

### ipmc-interfaces
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
"license": "MIT",
"private": true,
"type": "module",
"packageManager": "yarn@4.3.1",
"packageManager": "yarn@4.5.3",
"scripts": {
"watch": "yarn workspaces foreach -Api run watch",
"build": "yarn workspaces foreach -At run build",
"test": "yarn workspaces foreach -At run test",
"cli": "yarn workspace ipmc-tools run cli"
},
"workspaces": [
"packages/*"
],
"devDependencies": {
"typescript": "^5.4.5"
"typescript": "^5.7.2"
}
}
12 changes: 7 additions & 5 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@
"name": "ipmc-core",
"main": "./dist/index.js",
"type": "module",
"packageManager": "[email protected]",
"scripts": {
"watch": "tsc --watch",
"build": "tsc"
"build": "tsc",
"test": "vitest run"
},
"dependencies": {
"inversify": "^6.0.2",
"i18next": "^24.2.0",
"inversify": "^6.2.1",
"ipmc-interfaces": "workspace:^",
"kubo-rpc-client": "^4.1.1",
"kubo-rpc-client": "^5.0.2",
"reflect-metadata": "^0.2.2"
},
"devDependencies": {
"typescript": "^5.4.5"
"typescript": "^5.7.2",
"vitest": "^2.1.8"
}
}
4 changes: 4 additions & 0 deletions packages/core/src/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export class Application implements IApplication, IApplicationRegistration {
this.container.bind<T>(identifier).toConstantValue(service);
}

public registerConstantMultiple<T>(service: T, identifier: symbol) {
this.container.bind<T>(identifier).toConstantValue(service);
}

public register<T>(service: interfaces.Newable<T>, identifier: symbol) {
if (this.container.isBound(identifier)) {
this.container.unbind(identifier);
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/Defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const Defaults = {
Bootstrap: [
// a list of bootstrap peer multiaddrs to connect to on node startup
'/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ',
'/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN',
'/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',
]
};
7 changes: 7 additions & 0 deletions packages/core/src/IApplicationRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export interface IApplicationRegistration {
*/
registerConstant<T>(service: T, identifier: symbol): void;

/**
* Registers a new constant without removing previous registrations.
* @param service service to register.
* @param identifier symbol for the service.
*/
registerConstantMultiple<T>(service: T, identifier: symbol): void;

/**
* Registers a {@link IModule}.
* @param module {@link IModule} to use in the {@link IApplication}.
Expand Down
14 changes: 13 additions & 1 deletion packages/core/src/Modules/CoreModule.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import { IIndexManagerSymbol, IKeyValueStoreSymbol, IObjectStoreSymbol } from 'ipmc-interfaces';
import { IIndexManagerSymbol, IKeyValueStoreSymbol, IObjectStoreSymbol, ITranslationServiceSymbol, ITranslationsSymbol } from 'ipmc-interfaces';
import { MemoryKeyValueStore } from '../Services/MemoryKeyValueStore';
import { ObjectStore } from '../Services/ObjectStore';
import { IModule } from './IModule';
import { IndexManager } from '../Services/IndexManager';
import { TaskManager } from '../Services/TaskManager';
import { ITaskManagerSymbol } from 'ipmc-interfaces';
import { TranslationService } from '../Services/TranslationService';
import en from '../translations/en.json';
import de from '../translations/de.json';

export const CoreModule: IModule = (app) => {
app.register(MemoryKeyValueStore, IKeyValueStoreSymbol);
app.register(ObjectStore, IObjectStoreSymbol);
app.register(IndexManager, IIndexManagerSymbol);
app.register(TaskManager, ITaskManagerSymbol);
app.register(TranslationService, ITranslationServiceSymbol);
app.registerConstantMultiple({
en: {
translation: en,
},
de: {
translation: de,
}
}, ITranslationsSymbol);
};
5 changes: 3 additions & 2 deletions packages/core/src/Regexes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const Regexes = {
VideoFile: /.mp4$/,
VideoFile: (ext = 'mpd') => new RegExp(`^([\\w\\s':\\.\\-&!]+)(?: \\((\\d{4})\\))?\\.${ext}$`),
Thumbnail: /thumb\d*\.(jpg|jpeg|png)$/,
Poster: /poster\d*\.(jpg|jpeg|png)$/,
}
LangCheck: /^((?:(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?:([A-Za-z]{2,3}(-(?:[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?:[A-Za-z]{4}))?(-(?:[A-Za-z]{2}|[0-9]{3}))?(-(?:[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?:[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?:x(-[A-Za-z0-9]{1,8})+))?)|(?:x(-[A-Za-z0-9]{1,8})+))$/i // Source: https://www.regextester.com/103066
};
42 changes: 24 additions & 18 deletions packages/core/src/Services/IndexManager.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { Signal } from '@preact/signals-core';
import { inject, injectable, postConstruct, preDestroy } from 'inversify';
import { IIndexManager, IIpfsService, IIpfsServiceSymbol, ILibrary, ILibraryIndex, IObjectStore, IObjectStoreSymbol, IProfile, IProfileSymbol, ITask, isMovieLibrary, isSeriesLibrary } from 'ipmc-interfaces';
import { MovieIndexFetcher, SeriesIndexFetcher } from './Indexer';
import { ITaskManager, ITaskManagerSymbol } from 'ipmc-interfaces';
import { IIndexManager, IIpfsService, IIpfsServiceSymbol, ILibrary, ILibraryIndex, IObjectStore, IObjectStoreSymbol, IOnProgress, IProfile, IProfileSymbol, ITask, ITaskManager, ITaskManagerSymbol, ITranslationService, ITranslationServiceSymbol } from 'ipmc-interfaces';
import { IIndexFetcher, MovieIndexFetcher, SeriesIndexFetcher } from './Indexer';

@injectable()
export class IndexManager implements IIndexManager {
public constructor(
@inject(IProfileSymbol) private readonly profile: IProfile,
@inject(IIpfsServiceSymbol) private readonly ipfs: IIpfsService,
@inject(IObjectStoreSymbol) private readonly objectStore: IObjectStore,
@inject(ITaskManagerSymbol) private readonly taskManager: ITaskManager
@inject(ITaskManagerSymbol) private readonly taskManager: ITaskManager,
@inject(ITranslationServiceSymbol) private readonly translationService: ITranslationService,
) {
this.indexers.push(new MovieIndexFetcher(this.ipfs));
this.indexers.push(new SeriesIndexFetcher(this.ipfs));

for (const lib of this.profile.libraries) {
this.libraries.set(lib.name, new Signal<ILibrary>(lib));
const indexSignal = new Signal<ILibraryIndex<any> | undefined>(this.objectStore.get(this.getIndexStorageKey(lib.name)));
Expand Down Expand Up @@ -39,39 +42,40 @@ export class IndexManager implements IIndexManager {

public indexes = new Map<string, Signal<ILibraryIndex<any> | undefined>>();

public tasks = new Signal<ITask[]>([]);

private getIndexStorageKey(name: string) {
return `${this.profile.id}_index_${name}`;
}

private triggerUpdate(): void {
for (const library of this.libraries.values()) {
const lib = library.value;
if (!this.updates.has(lib.name)) {
this.taskManager.runTask({
task: () => this.updateLibrary(lib),
title: '',
onEnd: () => {
this.updates.delete(lib.name);
},
});
if (this.updates.has(lib.name)) {
this.updates.get(lib.name)!.abort();
}
const controller = new AbortController();
this.updates.set(lib.name, controller);
this.taskManager.runTask({
task: (onProgress) => this.updateLibrary(lib, controller.signal, onProgress),
title: this.translationService.translate('UpdatingLibrary', { name: lib.name }),
onEnd: () => {
this.updates.delete(lib.name);
},
});
}
}

private async updateLibrary(library: ILibrary): Promise<void> {
private async updateLibrary(library: ILibrary, signal: AbortSignal, onProgress: IOnProgress): Promise<void> {
const index = this.indexes.get(library.name);
if (library.upstream != undefined && index != undefined) {
try {
const cid = await this.ipfs.resolve(library.upstream);
const indexer = isMovieLibrary(library) ? new MovieIndexFetcher(this.ipfs) : isSeriesLibrary(library) ? new SeriesIndexFetcher(this.ipfs) : undefined;
const indexer = this.indexers.find(i => i.canIndex(library));
if (index.value?.cid != cid || indexer?.version !== index.value.indexer) {
if (indexer == undefined) {
throw new Error(`Unknown library type [${library.type}]`);
}

const newIndex = await indexer.fetchIndex(cid);
const newIndex = await indexer.fetchIndex(cid, signal, onProgress);

index.value = {
cid: cid,
Expand All @@ -85,9 +89,11 @@ export class IndexManager implements IIndexManager {
}
}

private indexers: IIndexFetcher<any>[] = [];

private libraries = new Map<string, Signal<ILibrary>>();

private updates = new Map<string, Promise<void>>();
private updates = new Map<string, AbortController>();

private timer: any;
}
20 changes: 19 additions & 1 deletion packages/core/src/Services/Indexer/IIndexFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
import { ILibrary, IOnProgress } from 'ipmc-interfaces';

export interface IIndexFetcher<TIndex> {
fetchIndex(cid: string): Promise<TIndex>;
/**
* Fetches the index of the specified CID.
* @param cid cid to index.
* @param abortSignal signal to abort the process.
* @param onProgress function to update progress.
*/
fetchIndex(cid: string, abortSignal: AbortSignal, onProgress: IOnProgress): Promise<TIndex>;

/**
* Checks wheter the indexer can handler specified {@link ILibrary}.
* @param library library to check compatibility for.
*/
canIndex(library: ILibrary): boolean;

/**
* Version of the indexer.
*/
version: string;
}
31 changes: 24 additions & 7 deletions packages/core/src/Services/Indexer/MovieIndexFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IFileInfo, IIpfsService, IMovieMetaData } from 'ipmc-interfaces';
import { IFileInfo, IIpfsService, ILibrary, IMovieMetaData, IOnProgress } from 'ipmc-interfaces';
import { Regexes } from '../../Regexes';
import { IIndexFetcher } from './IIndexFetcher';

Expand All @@ -8,25 +8,42 @@ export class MovieIndexFetcher implements IIndexFetcher<IMovieMetaData[]> {

public version = '0';

public async fetchIndex(cid: string): Promise<IMovieMetaData[]> {
public async fetchIndex(cid: string, signal: AbortSignal, onProgress: IOnProgress): Promise<IMovieMetaData[]> {
const files = (await this.node.ls(cid)).filter(f => f.type == 'dir');
signal.throwIfAborted();
const index = [];
for (const file of files) {
index.push(await this.extractMovieMetaData(this.node, file));
for (const [i, file] of files.entries()) {
try {
index.push(await this.extractMovieMetaData(file, signal));
} catch (ex) {
console.error(ex);
}
signal.throwIfAborted();
onProgress(i, files.length);
}

return index;
}

public async extractMovieMetaData(node: IIpfsService, entry: IFileInfo, skeleton?: any): Promise<IMovieMetaData> {
public async extractMovieMetaData(entry: IFileInfo, signal: AbortSignal, skeleton?: any): Promise<IMovieMetaData> {
const files = (await this.node.ls(entry.cid)).filter(f => f.type == 'file');
const videoFile = files.find(f => Regexes.VideoFile('mpd').exec(f.name) != null);

if (!videoFile) throw new Error('Failed to find video file in ' + entry.name + '|' + entry.cid);

const videoData = Regexes.VideoFile('mpd').exec(videoFile.name)!;

return {
...entry,
title: entry.name,
video: files.filter(f => f.name.endsWith('.mp4'))[0],
title: videoData[1],
year: videoData[2] != null ? parseInt(videoData[2]) : undefined,
video: videoFile,
thumbnails: files.filter(f => Regexes.Thumbnail.exec(f.name) != null),
posters: files.filter(f => Regexes.Poster.exec(f.name) != null),
};
}

public canIndex(library: ILibrary): boolean {
return library.type === 'movie';
}
}
8 changes: 6 additions & 2 deletions packages/core/src/Services/Indexer/SeriesIndexFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IEpisodeMetaData, IFileInfo, IIpfsService, ISeasonMetaData, ISeriesMetaData } from 'ipmc-interfaces';
import { IEpisodeMetaData, IFileInfo, IIpfsService, ILibrary, ISeasonMetaData, ISeriesMetaData } from 'ipmc-interfaces';
import { Regexes } from '../../Regexes';
import { IIndexFetcher } from './IIndexFetcher';

Expand Down Expand Up @@ -62,7 +62,7 @@ export class SeriesIndexFetcher implements IIndexFetcher<ISeriesMetaData[]> {
...entry,
posters: files.filter(f => Regexes.Poster.exec(f.name) != null),
title: entry.name,
video: files.filter(f => f.name.endsWith('.mp4'))[0],
video: files.filter(f => f.name.endsWith('.mpd'))[0],
thumbnails: files.filter(f => Regexes.Thumbnail.exec(f.name) != null),
};

Expand All @@ -72,4 +72,8 @@ export class SeriesIndexFetcher implements IIndexFetcher<ISeriesMetaData[]> {

return episode;
}

public canIndex(library: ILibrary): boolean {
return library.type === 'series';
}
}
Loading

0 comments on commit 72a716e

Please sign in to comment.