diff --git a/package.json b/package.json index af970515..e3c81145 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "dependencies": { "@sentry/electron": "^3.0.7", "@sentry/integrations": "^6.19.6", + "@types/pouchdb": "^6.4.0", + "@types/pouchdb-node": "^6.1.4", "adm-zip": "0.4.10", "airtunes2": "git+https://github.com/ciderapp/node_airtunes2", "castv2-client": "^1.2.0", @@ -58,11 +60,14 @@ "jimp": "^0.16.1", "jsonc": "^2.0.0", "lastfmapi": "^0.1.1", + "level": "^8.0.0", "mdns-js": "git+https://github.com/ciderapp/node-mdns-js.git", "mpris-service": "^2.1.2", "music-metadata": "^7.12.3", "node-gyp": "^9.0.0", "node-ssdp": "^4.0.1", + "pouchdb-adapter-leveldb": "^7.3.0", + "pouchdb-node": "^7.3.0", "qrcode": "^1.5.0", "react": "^18.0.0", "react-dom": "^18.0.0", diff --git a/src/main/base/browserwindow.ts b/src/main/base/browserwindow.ts index caf2d4e5..46023194 100644 --- a/src/main/base/browserwindow.ts +++ b/src/main/base/browserwindow.ts @@ -1,5 +1,5 @@ -import {join} from "path"; -import {app, BrowserWindow as bw, ipcMain, ShareMenu, shell, screen, dialog} from "electron"; +import { join } from "path"; +import { app, BrowserWindow as bw, ipcMain, ShareMenu, shell, screen, dialog } from "electron"; import * as windowStateKeeper from "electron-window-state"; import * as express from "express"; import * as getPort from "get-port"; @@ -27,8 +27,7 @@ import { watch } from "chokidar"; import * as os from "os"; import wallpaper from "wallpaper"; import * as AdmZip from "adm-zip"; -import * as path from 'path'; -const { readdir } = require('fs').promises; +import { LocalFiles } from "../providers/local/"; /** @@ -40,12 +39,11 @@ const { readdir } = require('fs').promises; export class BrowserWindow { public static win: any | undefined = null; private devMode: boolean = !app.isPackaged; + public static express: any | undefined = null; private audioStream: any = new Stream.PassThrough(); private headerSent: any = false; private chromecastIP: any = []; - private localSongs: any = []; - private localSongsArts: any = []; private clientPort: number = 0; private remotePort: number = 6942; private EnvironmentVariables: object = { @@ -410,9 +408,10 @@ export class BrowserWindow { } // Start the webserver for the browser window to load - + // LocalFiles.DB.init() this.startWebServer(); + BrowserWindow.win = new bw(this.options); // cant be built in CI // if (process.platform === "win32" && (utils.getStoreValue('visual.transparent') ?? false)) { @@ -468,7 +467,7 @@ export class BrowserWindow { */ private startWebServer(): void { const app = express(); - + BrowserWindow.express = app; app.use(express.static(join(utils.getPath('srcPath'), "./renderer/"))); app.set("views", join(utils.getPath('srcPath'), "./renderer/views")); app.set("view engine", "ejs"); @@ -553,23 +552,6 @@ export class BrowserWindow { } }); - app.get("/ciderlocal/:songs", (req, res) => { - const audio = atob(req.params.songs.replace(/_/g, '/').replace(/-/g, '+')); - //console.log('auss', audio) - let data = {data: - this.localSongs.filter((f: any) => audio.split(',').includes(f.id))}; - res.send(data); - }); - - app.get("/ciderlocalart/:songs", (req, res) => { - const audio = req.params.songs; - // metadata.common.picture[0].data.toString('base64') - let data = - this.localSongsArts.filter((f: any) => f.id == audio); - res.status(200).send(Buffer.from(data[0]?.url, 'base64')); - }); - - app.get("/themes/:theme/*", (req: { params: { theme: string, 0: string } }, res) => { const theme = req.params.theme; const file = req.params[0]; @@ -632,6 +614,9 @@ export class BrowserWindow { utils.getWindow().webContents.send('setStoreValue', 'connectUser', JSON.parse(req.params.data)) res.redirect(`https://connect.cidercollective.dev/linked.html`) }); + + LocalFiles.setupHandlers() + // [Connect] Set auth URL in store for `shell.openExternal` utils.setStoreValue('cc_authURL', `https://connect.cidercollective.dev/callback/discord?app=cider&appPort=${this.clientPort}`) console.log(`[Connect] Auth URL: ${utils.getStoreValue('cc_authURL')}`) @@ -1199,95 +1184,10 @@ export class BrowserWindow { }); - ipcMain.on("scanLibrary", async (event, folders) => { - async function getFiles(dir: any) { - const dirents = await readdir(dir, { withFileTypes: true }); - const files = await Promise.all(dirents.map((dirent: any) => { - const res = path.resolve(dir, dirent.name); - return dirent.isDirectory() ? getFiles(res) : res; - })); - return Array.prototype.concat(...files); - } - if (folders == null || folders.length == null || folders.length == 0) folders = ["D:\\Music"] - console.log('folders', folders) - let files: any[] = [] - for (var folder of folders){ - // get files from the Music folder - files = files.concat(await getFiles(folder)) - } - - //console.log("cider.files", files2); - let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"] - let audiofiles = files.filter(f => supporttedformats.includes(f.substring(f.lastIndexOf('.') + 1))); - // console.log("cider.files2", audiofiles, audiofiles.length); - let metadatalist = [] - let metadatalistart = [] - let numid = 0; - for (var audio of audiofiles) { - try{ - const metadata = await mm.parseFile(audio); - if (metadata != null){ - let form = { - "id": "ciderlocal" + numid, - "type": "podcast-episodes", - "href": audio, - "attributes": { - "artwork": { - "width": 3000, - "height": 3000, - "url": "/ciderlocalart/" + "ciderlocal" + numid, - }, - "topics": [], - "url": "", - "subscribable": true, - "mediaKind": "audio", - "genreNames": [ - "" - ], - // "playParams": { - // "id": "ciderlocal" + numid, - // "kind": "podcast", - // "isLibrary": true, - // "reporting": false }, - "trackNumber": metadata.common.track?.no ?? 0, - "discNumber": metadata.common.disk?.no ?? 0, - "name": metadata.common.title ?? audio.substring(audio.lastIndexOf('\\') + 1), - "albumName": metadata.common.album, - "artistName": metadata.common.artist, - "copyright": metadata.common.copyright ?? "", - "assetUrl": "file:///" +audio, - "contentAdvisory": "", - "releaseDateTime": `${metadata?.common?.year ?? '2022'}-05-13T00:23:00Z`, - "durationInMillis": Math.floor((metadata.format.duration?? 0) * 1000), - - "offers": [ - { - "kind": "get", - "type": "STDQ" - } - ], - "contentRating": "clean" - } - }; - metadatalistart.push({ - id : "ciderlocal" + numid, - url: metadata.common.picture != undefined ? metadata.common.picture[0].data.toString('base64') : "", - }) - numid += 1; - metadatalist.push(form)} - } catch (e){} - } - // console.log('metadatalist', metadatalist); - this.localSongs = metadatalist; - this.localSongsArts = metadatalistart; - BrowserWindow.win.webContents.send('getUpdatedLocalList', metadatalist); - } - // console.log('metadatalist', metadatalist); - this.localSongs = metadatalist; + ipcMain.handle("scanLibrary", async (event, folders) => { + const metadatalist = await LocalFiles.scanLibrary() BrowserWindow.win.webContents.send('getUpdatedLocalList', metadatalist); - } - - ) + }) ipcMain.on('writeWAV', (event, leftpcm, rightpcm, bufferlength) => { @@ -1487,7 +1387,7 @@ export class BrowserWindow { ipcMain.handle('folderSelector', async (_event) => { let u = await dialog.showOpenDialog({ - properties: ['openDirectory','multiSelections'] + properties: ['openDirectory', 'multiSelections'] }); return u.filePaths }); diff --git a/src/main/base/store.ts b/src/main/base/store.ts index a4fda8a7..a014ae45 100644 --- a/src/main/base/store.ts +++ b/src/main/base/store.ts @@ -31,7 +31,8 @@ export class Store { "applemusic": false, "library": false, "amplaylists": false, - "playlists": false + "playlists": false, + "localLibrary": false }, "onStartup": { "enabled": false, diff --git a/src/main/base/utils.ts b/src/main/base/utils.ts index 46b865be..f2108528 100644 --- a/src/main/base/utils.ts +++ b/src/main/base/utils.ts @@ -67,6 +67,14 @@ export class utils { static getIPCMain(): Electron.IpcMain { return ipcMain } + + /* + * Get the Express instance + * @returns {any} + */ + static getExpress(): any { + return bw.express + } /** * Fetches the i18n locale for the given language. diff --git a/src/main/index.ts b/src/main/index.ts index 4a74f0e9..a5a2de99 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -13,7 +13,9 @@ import {BrowserWindow} from "./base/browserwindow"; import {init as Sentry} from "@sentry/electron"; import {RewriteFrames} from "@sentry/integrations"; import {components, ipcMain} from "electron" +import {ProviderDB} from "./providers/local/db"; +ProviderDB.init() // Analytics for debugging fun yeah. Sentry({ diff --git a/src/main/providers/local/db/index.ts b/src/main/providers/local/db/index.ts new file mode 100644 index 00000000..0add4b93 --- /dev/null +++ b/src/main/providers/local/db/index.ts @@ -0,0 +1,8 @@ +import * as PouchDB from 'pouchdb-node'; + +export class ProviderDB { + public static db: any = null + static async init() { + // ProviderDB.db = new PouchDB('tracks') + } +} \ No newline at end of file diff --git a/src/main/providers/local/index.ts b/src/main/providers/local/index.ts new file mode 100644 index 00000000..1c95e0f3 --- /dev/null +++ b/src/main/providers/local/index.ts @@ -0,0 +1,122 @@ +// import { ProviderDB } from "./db"; +import * as path from 'path'; +const { readdir } = require('fs').promises; +import { utils } from '../../base/utils'; +import * as mm from 'music-metadata'; + +export class LocalFiles { + static localSongs: any = []; + static localSongsArts: any = []; + // public static DB = ProviderDB.db; + + + static async scanLibrary() { + let folders = utils.getStoreValue("libraryPrefs.localPaths") + if (folders == null || folders.length == null || folders.length == 0) folders = ["D:\\Music"] + console.log('folders', folders) + let files: any[] = [] + for (var folder of folders) { + // get files from the Music folder + files = files.concat(await LocalFiles.getFiles(folder)) + } + + //console.log("cider.files", files2); + let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"] + let audiofiles = files.filter(f => supporttedformats.includes(f.substring(f.lastIndexOf('.') + 1))); + // console.log("cider.files2", audiofiles, audiofiles.length); + let metadatalist = [] + let metadatalistart = [] + let numid = 0; + for (var audio of audiofiles) { + try { + const metadata = await mm.parseFile(audio); + if (metadata != null) { + let form = { + "id": "ciderlocal" + numid, + "type": "podcast-episodes", + "href": audio, + "attributes": { + "artwork": { + "width": 3000, + "height": 3000, + "url": "/ciderlocalart/" + "ciderlocal" + numid, + }, + "topics": [], + "url": "", + "subscribable": true, + "mediaKind": "audio", + "genreNames": [ + "" + ], + // "playParams": { + // "id": "ciderlocal" + numid, + // "kind": "podcast", + // "isLibrary": true, + // "reporting": false }, + "trackNumber": metadata.common.track?.no ?? 0, + "discNumber": metadata.common.disk?.no ?? 0, + "name": metadata.common.title ?? audio.substring(audio.lastIndexOf('\\') + 1), + "albumName": metadata.common.album, + "artistName": metadata.common.artist, + "copyright": metadata.common.copyright ?? "", + "assetUrl": "file:///" + audio, + "contentAdvisory": "", + "releaseDateTime": `${metadata?.common?.year ?? '2022'}-05-13T00:23:00Z`, + "durationInMillis": Math.floor((metadata.format.duration ?? 0) * 1000), + + "offers": [ + { + "kind": "get", + "type": "STDQ" + } + ], + "contentRating": "clean" + } + }; + metadatalistart.push({ + id: "ciderlocal" + numid, + url: metadata.common.picture != undefined ? metadata.common.picture[0].data.toString('base64') : "", + }) + numid += 1; + metadatalist.push(form) + } + } catch (e) { } + } + // console.log('metadatalist', metadatalist); + this.localSongs = metadatalist; + this.localSongsArts = metadatalistart; + return metadatalist; + } + static async getFiles(dir: any) { + const dirents = await readdir(dir, { withFileTypes: true }); + const files = await Promise.all(dirents.map((dirent: any) => { + const res = path.resolve(dir, dirent.name); + return dirent.isDirectory() ? this.getFiles(res) : res; + })); + return Array.prototype.concat(...files); + } + + static setupHandlers () { + const app = utils.getExpress() + console.log("Setting up handlers for local files") + app.get("/ciderlocal/:songs", (req: any, res: any) => { + const audio = atob(req.params.songs.replace(/_/g, '/').replace(/-/g, '+')); + //console.log('auss', audio) + let data = { + data: + LocalFiles.localSongs.filter((f: any) => audio.split(',').includes(f.id)) + }; + res.send(data); + }); + + app.get("/ciderlocalart/:songs", (req: any, res: any) => { + const audio = req.params.songs; + // metadata.common.picture[0].data.toString('base64') + let data = + LocalFiles.localSongsArts.filter((f: any) => f.id == audio); + res.status(200).send(Buffer.from(data[0]?.url, 'base64')); + }); + + return app + } +} \ No newline at end of file diff --git a/src/renderer/main/vueapp.js b/src/renderer/main/vueapp.js index a7ccc81c..86638d0e 100644 --- a/src/renderer/main/vueapp.js +++ b/src/renderer/main/vueapp.js @@ -1074,7 +1074,7 @@ const app = new Vue({ this.checkForThemeUpdates() } - ipcRenderer.send("scanLibrary",app.cfg.libraryPrefs.localPaths) + ipcRenderer.invoke("scanLibrary") }, showFoo(querySelector, time) { clearTimeout(this.idleTimer); diff --git a/src/renderer/views/components/pathmenu.ejs b/src/renderer/views/components/pathmenu.ejs index 560bc1e4..05e6db0a 100644 --- a/src/renderer/views/components/pathmenu.ejs +++ b/src/renderer/views/components/pathmenu.ejs @@ -53,12 +53,12 @@ } } this.$root.cfg.libraryPrefs.localPaths = this.folders; - ipcRenderer.send("scanLibrary",app.cfg.libraryPrefs.localPaths) + ipcRenderer.invoke("scanLibrary") }, remove(dir){ this.folders = this.folders.filter(item => item !== dir) this.$root.cfg.libraryPrefs.localPaths = this.folders; - ipcRenderer.send("scanLibrary",app.cfg.libraryPrefs.localPaths) + ipcRenderer.invoke("scanLibrary") } } }); diff --git a/tsconfig.json b/tsconfig.json index f9e5503c..c1b41500 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "paths": { "*": ["node_modules/*"] }, + "skipLibCheck": true, "allowSyntheticDefaultImports": true, "typeRoots": [ "node_modules/musickit-typescript",