From 9e99502c7fa876a37519fc4a84c2ce311c141034 Mon Sep 17 00:00:00 2001 From: Patrick-web Date: Mon, 11 Oct 2021 09:15:06 +0300 Subject: [PATCH] Rewrote the folder parser,notifications manager and tageditor --- .env | 1 + package-lock.json | 108 +++++++- package.json | 10 +- src/background.ts | 234 +++++------------- src/main/core/createParsedTrack copy.ts | 68 +++++ src/main/core/parseFolder.ts | 72 ++++++ src/main/core/utils.ts | 67 +++++ src/main/modules/FilesTracker.ts | 39 +-- src/main/modules/UsageStatistics.ts | 20 +- src/main/reusables/messageToRenderer.ts | 13 + src/main/utils/index.ts | 18 +- src/renderer/assets/styles/themer.scss | 18 +- .../local-music/side-pane/tag-editor.vue | 39 +-- .../local-music/side-pane/track-info.vue | 44 +--- .../tabs-pane/home-tab/over-layed-tracks.vue | 8 +- src/renderer/components/root/ipc-listener.vue | 46 ++-- .../root/notifications/base-notification.vue | 41 +-- .../root/notifications/notifications.vue | 26 +- .../root/player/album-art-wrapper.vue | 23 +- .../components/root/player/playing-pane.vue | 59 +++-- .../local-music/notification-manager.ts | 28 ++- .../store/modules/local-music/tabs-manager.ts | 46 ++-- .../views/local-music/local-music.vue | 5 +- src/types/index.ts | 4 +- 24 files changed, 637 insertions(+), 400 deletions(-) create mode 100644 .env create mode 100644 src/main/core/createParsedTrack copy.ts create mode 100644 src/main/core/parseFolder.ts create mode 100644 src/main/core/utils.ts create mode 100644 src/main/reusables/messageToRenderer.ts diff --git a/.env b/.env new file mode 100644 index 0000000..58d1749 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYyNzMwODQ4OSwiZXhwIjoxOTQyODg0NDg5fQ.wr67SdghPIPEEhkoK7xeOHS3Dq2jojCYDYsD6ktUKTo \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c8fd2f2..dc1be17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "flbmusic", - "version": "1.1.6", + "version": "1.1.9", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1695,6 +1695,11 @@ "defer-to-connect": "^1.0.1" } }, + "@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, "@types/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", @@ -1823,6 +1828,11 @@ "@types/node": "*" } }, + "@types/lokijs": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@types/lokijs/-/lokijs-1.5.7.tgz", + "integrity": "sha512-WEFQLgO3u2Wa7yFybqkTZYumqF1GcHvUwx8Tv2SUHy2qpnIainMMoLmEUGdjhPNp/v5pyC9e91fSMC3mkxBIDw==" + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -2663,6 +2673,14 @@ "webpack-chain": "^6.4.0", "webpack-dev-server": "^3.11.0", "webpack-merge": "^4.2.2" + }, + "dependencies": { + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true + } } }, "@vue/cli-shared-utils": { @@ -5215,8 +5233,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "convert-source-map": { "version": "1.8.0", @@ -6346,10 +6363,9 @@ } }, "dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "dev": true + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" }, "dotenv-expand": { "version": "5.1.0", @@ -10946,6 +10962,11 @@ "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", "dev": true }, + "lokijs": { + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/lokijs/-/lokijs-1.5.12.tgz", + "integrity": "sha512-Q5ALD6JiS6xAUWCwX3taQmgwxyveCtIIuL08+ml0nHwT3k0S/GIFJN+Hd38b1qYIMaE5X++iqsqWVksz7SYW+Q==" + }, "lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", @@ -11441,6 +11462,36 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "music-metadata": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-7.11.4.tgz", + "integrity": "sha512-pEaS/vRo0zCAWJ0y5zZ5ruM8CvPPJ/VXbevRMUVkZkWN8rZAQun04xx09/3/PV0qS3nlrzPUDCRK6jDrbGVtUg==", + "requires": { + "content-type": "^1.0.4", + "debug": "^4.3.2", + "file-type": "16.5.3", + "media-typer": "^1.1.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "dependencies": { + "file-type": { + "version": "16.5.3", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.3.tgz", + "integrity": "sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A==", + "requires": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + } + }, + "media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" + } + } + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -12323,6 +12374,11 @@ "sha.js": "^2.4.8" } }, + "peek-readable": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.0.1.tgz", + "integrity": "sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ==" + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -13497,6 +13553,26 @@ "util-deprecate": "~1.0.1" } }, + "readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "requires": { + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -14958,6 +15034,15 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "strtok3": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.2.4.tgz", + "integrity": "sha512-GO8IcFF9GmFDvqduIspUBwCzCbqzegyVKIsSymcMgiZKeCfrN9SowtUoi8+b59WZMAjIzVZic/Ft97+pynR3Iw==", + "requires": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.0.1" + } + }, "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", @@ -15609,6 +15694,15 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", "dev": true }, + "token-types": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.1.1.tgz", + "integrity": "sha512-hD+QyuUAyI2spzsI0B7gf/jJ2ggR4RjkAo37j3StuePhApJUwcWDjnHDOFdIWYSwNR28H14hpwm4EI+V1Ted1w==", + "requires": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + } + }, "toposort": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", diff --git a/package.json b/package.json index c31219c..e470289 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,14 @@ "url": "git+https://github.com/FLB-Music/FLB-Music-Player.git" }, "author": { - "name": "Patrick Waweru (Just Patrick,JP)", + "name": "Patrick Waweru (Just Patrick)", "email": "pntxall100@gmail.com" }, "main": "background.js", "scripts": { "start": "vue-cli-service electron:serve --skip-plugins @vue/cli-plugin-eslint", - "serve": "vue-cli-service electron:serve", - "build": "vue-cli-service electron:build --skip-plugins @vue/cli-plugin-eslint", + "serve": "vue-cli-service serve", + "build": "vue-cli-service build", "test:unit": "vue-cli-service test:unit", "lint": "vue-cli-service lint --fix -f codeframe", "e:build": "vue-cli-service electron:build --skip-plugins @vue/cli-plugin-eslint", @@ -26,11 +26,13 @@ }, "dependencies": { "@supabase/supabase-js": "^1.21.0", + "@types/lokijs": "^1.5.7", "animate.css": "4.1.1", "axios": "^0.21.1", "color-thief-browser": "^2.0.2", "core-js": "3.15.2", "css-color-converter": "2.0.0", + "dotenv": "^10.0.0", "electron": "13.1.5", "electron-updater": "4.3.9", "g-i-s": "2.1.6", @@ -39,6 +41,8 @@ "is-online": "^9.0.1", "json5": "2.2.0", "lodash": "4.17.21", + "lokijs": "^1.5.12", + "music-metadata": "^7.11.4", "node-downloader-helper": "1.0.18", "node-id3": "0.2.3", "node-vibrant": "^3.2.1-alpha.1", diff --git a/src/background.ts b/src/background.ts index d56a0f7..39135d2 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,4 +1,5 @@ 'use strict'; +require('dotenv').config() import { app, @@ -23,13 +24,10 @@ import { createParsedTrack } from './main/core/createParsedTrack'; import { deleteFile, downloadFile, - isValidFileType, sendMessageToRenderer, sendNativeNotification } from './main/utils'; import { - FolderInfoType, - FolderType, SettingsType, TagChangesType, TrackType @@ -38,6 +36,9 @@ import { downloadArtistPicture } from './main/services'; import { SUPPORTED_FORMATS } from './main/utils/constants'; import { DownloadManager } from './main/modules/BingDownloader'; import { UsageManager } from './main/modules/UsageStatistics'; +import parseFolder from './main/core/parseFolder'; +import { sendNotificationToRenderer } from './main/reusables/messageToRenderer'; +import { initializeApp, resetApp } from './main/core/utils'; console.log(paths.appFolder); @@ -66,7 +67,7 @@ protocol.registerSchemesAsPrivileged([ // Globally accessible window object export let win: BrowserWindow; -async function createWindow () { +async function createWindow() { const primaryDisplay = screen.getPrimaryDisplay(); const { width, height } = primaryDisplay.workAreaSize; // Create the browser window. @@ -112,22 +113,22 @@ async function createWindow () { shell.openExternal(url); }); autoUpdater.on('checking-for-update', () => { - win.webContents.send('normalMsg', 'Checking for update...'); + sendNotificationToRenderer('Checking for update...'); }); autoUpdater.on('update-available', () => { - win.webContents.send('normalMsg', 'Update available.'); + sendNotificationToRenderer('Update available.'); setTimeout(() => { - win.webContents.send('normalMsg', 'Downloading available.'); + sendNotificationToRenderer('Downloading available.'); }, 1000); }); autoUpdater.on('update-not-available', () => { - win.webContents.send('normalMsg', 'No Update available.'); + sendNotificationToRenderer('No Update available.'); }); autoUpdater.on('error', err => { - win.webContents.send('dangerMsg', 'Error in auto-updater. ' + err); + sendNotificationToRenderer('Error in auto-updater.', '', 'warning'); }); autoUpdater.on('update-downloaded', () => { - win.webContents.send('normalMsg', 'Update Downloaded 🚀'); + sendNotificationToRenderer('Update Downloaded 🚀'); }); autoUpdater.on('download-progress', () => { win.webContents.send('downloadingUpdate', ''); @@ -182,38 +183,16 @@ ipcMain.on('initializeSettings', () => { win.webContents.send('userSettings', settings.getSettings); }); ipcMain.on('getFirstTracks', async () => { - refreshTracks(); + const tracks = await getParsedTracks(settings.getSettings.foldersToScan); + fileTracker.addMultipleTracks(tracks); + sendMessageToRenderer("addMultipleTracks", tracks) }); -ipcMain.on('initializeApp', async () => { - // Handle Open With FLB. Parse the files that is called as a second argument when running FLB - if (process.argv[1] && isValidFileType(process.argv[1])) { - const newTrack = await createParsedTrack(process.argv[1]); - win.webContents.send('newTrack', newTrack); - win.webContents.send('playThisTrack', newTrack); - } - // Remember to fix - const processedFiles = fileTracker.getTracks; - const playlists = playlistsTracker.getPlaylists; - const recentlyPlayedTracks = playbackStats.recentlyPlayedTracks; - if (processedFiles.length > 0) { - win.webContents.send('processedFiles', processedFiles); - win.webContents.send('userPlaylists', playlists); - win.webContents.send('recentlyPlayed', recentlyPlayedTracks); - refreshTracks(); - win.webContents.send('playStats', playbackStats.getPlayStats); - } +ipcMain.on('initializeApp', () => { + initializeApp(fileTracker, playlistsTracker, playbackStats) }); ipcMain.on('resetApp', () => { - deleteFile(paths.filesTrackerLocation, true); - deleteFile(paths.playbackStatsLocation, true); - deleteFile(paths.playlistsLocation, true); - deleteFile(paths.settingsLocation, true); - win.webContents.send('dangerMsg', 'Resetting FLB'); - setTimeout(() => { - app.relaunch(); - app.quit(); - }, 1000); + resetApp() }); ipcMain.on('updatePlaylists', (e, payload) => { @@ -221,14 +200,12 @@ ipcMain.on('updatePlaylists', (e, payload) => { }); ipcMain.on('addScanFolder', () => { - dialog.showOpenDialog(win, { properties: ['openDirectory'] }).then(data => { + dialog.showOpenDialog(win, { properties: ['openDirectory'] }).then(async (data) => { if (!data.canceled) { settings.addFolderToScan(data.filePaths[0]); win.webContents.send('userSettings', settings.getSettings); - if (fileTracker.getTracks.length) { - refreshTracks(); - win.webContents.send('normalMsg', 'Refreshing...'); - } + const tracks = await getParsedTracks([data.filePaths[0]]) + sendMessageToRenderer("addMultipleTracks", tracks) } }); }); @@ -239,7 +216,8 @@ ipcMain.on('removeFromScannedFolders', (e, payload) => { win.webContents.send('userSettings', settings.getSettings); }); ipcMain.on('refresh', () => { - refreshTracks(); + sendNotificationToRenderer("Refreshing...", "", "success") + getParsedTracks(settings.getSettings.foldersToScan); }); ipcMain.on('playingTrack', async (e, track: TrackType) => { @@ -254,34 +232,29 @@ ipcMain.on('playingTrack', async (e, track: TrackType) => { // win.webContents.send("mostPlayedArtists", playbackStats.mostPlayedTracks); }); -ipcMain.on('processDroppedFiles', (e, droppedFiles) => { - console.log(droppedFiles); - win.webContents.send( - 'normalMsg', - `Processing Dropped Files: ${droppedFiles}` +ipcMain.on('processDroppedFiles', async (e, droppedFiles: string[]) => { + sendNotificationToRenderer( + `Processing Dropped Stuff: ${droppedFiles}` ); - // User dropped an array of folders + // // User dropped an array of folders if (fs.lstatSync(droppedFiles[0]).isDirectory()) { console.log('User dropped folder(s)'); - droppedFiles.forEach((folder: string) => { - parseFolder(folder, [], []).then(data => { - prepareTracksForProcessing(data); - }); - }); + const tracks = await getParsedTracks([droppedFiles[0]]) + fileTracker.addMultipleTracks(tracks); + sendMessageToRenderer('addMultipleTracks', tracks); } else { // User dropped an array of files console.log('User dropped file(s)'); - droppedFiles.forEach(async (file: string) => { + for (const file of droppedFiles) { const fileType = path.parse(file).ext; if (SUPPORTED_FORMATS.includes(fileType)) { const newTrack = await createParsedTrack(file); + fileTracker.addFile(newTrack); win.webContents.send('newTrack', newTrack); - fileTracker.saveChanges(); } - }); - setTimeout(() => { - win.webContents.send('playNow'); - }, 1000); + } + fileTracker.saveChanges(); + win.webContents.send('playNow'); } }); ipcMain.on('updateSettings', async (e, payload: SettingsType) => { @@ -325,7 +298,8 @@ ipcMain.on('maximize', () => { win.maximize(); } }); -ipcMain.on('closeWindow', () => { +ipcMain.on('closeWindow', async () => { + await fileTracker.saveChanges() app.quit(); }); @@ -372,7 +346,7 @@ ipcMain.on('sendUsageStats', () => { }); ipcMain.on('checkForUpdate', () => { - sendMessageToRenderer('normalMsg', 'Checking For Update'); + sendNotificationToRenderer('Checking For Update'); checkForUpdates(); console.log(app.getAppMetrics()); }); @@ -392,124 +366,50 @@ ipcMain.on('toggleMiniMode', (e, payload) => { } }); -async function parseFolder ( - folderPath: string, - subFolders: Array, - foldersFinalData: Array -) { - return new Promise(resolve => { - (function recursiveReader ( - folderPath: string, - subFolders: Array, - foldersFinalData: Array - ) { - subFolders.shift(); - const folderObject_notParsed: FolderType = { - name: folderPath.replace(/(.*)[/\\]/, '').split('.')[0], - path: folderPath, - tracks: [] - }; - fs.readdir(folderPath, async (err, files: Array) => { - let newSubFolders = files.filter(file => - fs.lstatSync(path.join(folderPath, file)).isDirectory() - ); - newSubFolders = newSubFolders.map(item => path.join(folderPath, item)); - subFolders = [...subFolders, ...newSubFolders]; - const audioFiles = files.filter(file => - SUPPORTED_FORMATS.includes(path.parse(file).ext) - ); - const videoFiles = files.filter(file => file.match(/\.mp4|\.mkv/gi)); - folderObject_notParsed.tracks = audioFiles; - if (settings.getSettings.includeVideo) { - folderObject_notParsed.tracks = [ - ...folderObject_notParsed.tracks, - ...videoFiles - ]; - } - foldersFinalData = [...foldersFinalData, folderObject_notParsed]; - if (subFolders[0]) { - recursiveReader(subFolders[0], subFolders, foldersFinalData); - } else { - resolve(foldersFinalData); - console.log('Am Done Reading all the folders'); - } - }); - })(folderPath, subFolders, foldersFinalData); - }); -} -interface dataParamObj { - fileName: string; - filePath: string; - folder: FolderInfoType; -} -async function prepareTracksForProcessing (foldersFinalData: Array) { - const data: Array = []; - foldersFinalData.forEach(folder => { - folder.tracks.forEach(fileName => { - const filePath = path.join(folder.path, fileName); - const parsed = fileTracker.getTracks.some( - file => file.fileLocation === filePath - ); - if (!parsed) { - data.push({ fileName, filePath, folder }); - } - }); - }); - if (data.length !== 0) { - processTracks(data, 0); - } -} -async function processTracks (data: Array, index: number) { - console.log('Beginning to parse ' + data[index].fileName); - const newTrack = await createParsedTrack(data[index].filePath); - win.webContents.send('newTrack', newTrack); - console.log('Done parsing ' + data[index].fileName); - if (index !== data.length - 1) { - processTracks(data, index + 1); - win.webContents.send('parsingProgress', [index + 2, data.length]); - } else { - fileTracker.saveChanges(); - win.webContents.send('parsingDone', data.length); - return; - } -} +async function getParsedTracks(folders: string[] = []) { -function refreshTracks () { - const folders = settings.getSettings.foldersToScan; - console.log(folders); - let superFolder: FolderType[] = []; - handleAllFolders(folders, folders.length, 0); - function handleAllFolders (folders: string[], length: number, index: number) { - parseFolder(folders[index], [], []).then(data => { - superFolder = [...superFolder, ...data]; - index += 1; - if (index <= length - 1) { - handleAllFolders(folders, length, index); - } else { - prepareTracksForProcessing(superFolder); - } - }); + let allTracks: string[] = []; + const allParsedTracks: TrackType[] = []; + for (const folder of folders) { + const tracks = parseFolder(folder); + allTracks = [...allTracks, ...tracks] + } + for (const [index, track] of allTracks.entries()) { + sendNotificationToRenderer("Loading songs", `${index + 1}/${allTracks.length}`, 'normal', true) + const parsedTrack = await createParsedTrack(track) + allParsedTracks.push(parsedTrack) } + sendMessageToRenderer("closeNotification", 'Loading songs') + console.log(allParsedTracks.length + " tracks parsed"); + return allParsedTracks } -export async function writeTags ( +export async function writeTags( filePath: string, tagChanges: TagChangesType, silent = false ) { if (tagChanges.APIC && tagChanges.APIC.includes('http')) { - tagChanges.APIC = await downloadFile( - tagChanges.APIC, - paths.albumArtFolder, - path.parse(filePath).name - ); + console.log("downloading file"); + try { + tagChanges.APIC = await downloadFile( + tagChanges.APIC, + paths.albumArtFolder, + path.parse(filePath).name + ); + + } catch (error) { + console.log('errorobject'); + console.log(error); + } + console.log("Done downloading file"); tagChanges.APIC = decodeURI(tagChanges.APIC); } const isSuccessFull = NodeID3.update(tagChanges, filePath); console.log('Just Wrote'); if (isSuccessFull && !silent) { - sendMessageToRenderer('normalMsg', 'Tags Successfully changed'); + sendNotificationToRenderer('Tags Successfully changed', '', 'success'); console.log(filePath); // sendMessageToRenderer('updateTrack', { filePath, tagChanges }); } else { @@ -519,7 +419,7 @@ export async function writeTags ( } -function checkForUpdates () { +function checkForUpdates() { autoUpdater.checkForUpdatesAndNotify(); } diff --git a/src/main/core/createParsedTrack copy.ts b/src/main/core/createParsedTrack copy.ts new file mode 100644 index 0000000..4a99597 --- /dev/null +++ b/src/main/core/createParsedTrack copy.ts @@ -0,0 +1,68 @@ +import fs from 'fs'; +import path from 'path'; +import NodeID3 from 'node-id3'; +import { TrackType } from '@/types'; +import { paths } from '../modules/Paths'; +import { fileTracker } from '../../background'; +import { extractTitleAndArtist, removeMIME, writeImageBuffer } from '../utils'; + +export function createParsedTrack (fileLocation: string) { + return new Promise(resolve => { + const track: TrackType = { + r_fileLocation: '', + fileLocation: '', + albumArt: '', + album: '', + title: '', + artist: '', + extractedTitle: '', + defaultTitle: '', + extractedArtist: '', + defaultArtist: '', + fileName: '', + formattedLength: '', + duration: '', + dateAdded: 0, + folderInfo: { + name: path.parse(path.parse(fileLocation).dir).base, + path: path.parse(fileLocation).dir + } + }; + track.fileLocation = fileLocation; + track.r_fileLocation = 'file://' + fileLocation; + track.fileName = path.parse(fileLocation).name; + console.log('Parsing: ' + track.fileName); + NodeID3.read(fileLocation, async (err: any, tags: any) => { + if (tags && tags.image && tags.image.imageBuffer) { + tags.image.mime = tags.image.mime + ? tags.image.mime.replace(/image\//g, '') + : 'jpg'; + const albumArtPath = path.join( + paths.albumArtFolder, + `${removeMIME(track.fileName)}.${tags.image.mime}` + ); + writeImageBuffer(tags.image.imageBuffer, albumArtPath); + track.albumArt = albumArtPath; + } + track.title = tags.title; + track.extractedTitle = extractTitleAndArtist(track.fileName).title; + + track.artist = tags.artist; + track.extractedArtist = extractTitleAndArtist(track.fileName).artist; + + track.album = tags.album || 'unknown'; + + track.defaultTitle = + track.title || track.extractedTitle || track.fileName; + + track.defaultArtist = track.artist || track.extractedArtist; + + fs.stat(track.fileLocation, (err, stats) => { + track.dateAdded = stats.ctimeMs; + }); + + fileTracker.addFile(track); + resolve(track); + }); + }); +} diff --git a/src/main/core/parseFolder.ts b/src/main/core/parseFolder.ts new file mode 100644 index 0000000..c5f3e4f --- /dev/null +++ b/src/main/core/parseFolder.ts @@ -0,0 +1,72 @@ + +interface FolderContent { + name: string; + extension: string; +} + +interface ParsedPath { + root: string; + dir: string; + base: string; + ext: string; + name: string; +} + +import fs from 'fs'; +import path from 'path'; + +export default function parseFolder( + folder: string, + isStartingFolder = true, + subFolderStack: string[] = [], + filesStack: string[] = [] +): string[] { + console.log('**********' + folder + '**********'); + if (!isStartingFolder && subFolderStack.length == 0) { + console.log('===================Done===================='); + return filesStack; + } + const supportedFileTypes = [ + '.mp3', + '.wav', + '.ogg', + '.aac', + '.flac', + '.webm', + '.m4a' + ]; + try { + const folderContents: FolderContent[] = fs + .readdirSync(folder) + .map((content: string) => path.join(folder, content)) + .map((content: string) => path.parse(content)) + .map((pathObject: ParsedPath) => { + const po: FolderContent = { name: pathObject.name, extension: pathObject.ext }; + return po; + }); + + const subFolders: string[] = folderContents + .filter(content => content.extension == '') + .map(subFolder => path.join(folder, subFolder.name)); + + const files = folderContents + .filter( + content => + content.extension != '' && + supportedFileTypes.includes(content.extension) + ) + .map(file => path.join(folder, file.name) + file.extension); + + filesStack = [...filesStack, ...files]; + + subFolderStack = [...subFolderStack, ...subFolders]; + } catch (error) { + console.log('Skipped ' + folder + ' because'); + console.log(error); + subFolderStack.shift(); + } finally { + const firstSubFolder: string = subFolderStack.shift() || ''; + return parseFolder(firstSubFolder, false, subFolderStack, filesStack); + } +} + diff --git a/src/main/core/utils.ts b/src/main/core/utils.ts new file mode 100644 index 0000000..b327e51 --- /dev/null +++ b/src/main/core/utils.ts @@ -0,0 +1,67 @@ +import { win } from "@/background"; +import path from 'path'; +import { TrackType } from "@/types"; +import { FilesTracker } from "../modules/FilesTracker"; +import { PlaybackStats } from "../modules/PlaybackStats"; +import { PlaylistsTracker } from "../modules/PlaylistTracker"; +import { sendNotificationToRenderer } from "../reusables/messageToRenderer"; +import { deleteFile, isValidFileType, sendMessageToRenderer } from "../utils"; +import { createParsedTrack } from "./createParsedTrack"; +import parseFolder from "./parseFolder"; +import { app } from "electron"; +import { paths } from "../modules/Paths"; + +async function getParsedTracks(folders: string[] = []) { + + let allTracks: string[] = []; + const allParsedTracks: TrackType[] = []; + for (const folder of folders) { + const tracks = parseFolder(folder); + allTracks = [...allTracks, ...tracks] + } + for (const [index, track] of allTracks.entries()) { + sendNotificationToRenderer("Loading songs", `${index + 1}/${allTracks.length}`, 'normal', true) + const parsedTrack = await createParsedTrack(track) + allParsedTracks.push(parsedTrack) + } + sendMessageToRenderer("closeNotification", 'Loading songs') + console.log(allParsedTracks.length + " tracks parsed"); + return allParsedTracks +} + +export async function initializeApp(fileTracker: FilesTracker, playlistsTracker: PlaylistsTracker, playbackStats: PlaybackStats) { + // Handle Open With FLB. Parse the files that is called as a second argument when running FLB + const firstArgument = process.argv[1]; + if (firstArgument) { + if (isValidFileType(firstArgument)) { + const newTrack = await createParsedTrack(process.argv[1]); + win.webContents.send('newTrack', newTrack); + win.webContents.send('playThisTrack', newTrack); + } + if (path.parse(firstArgument).ext == '') { + const tracks = await getParsedTracks([firstArgument]) + fileTracker.addMultipleTracks(tracks); + sendMessageToRenderer("addMultipleTracks", tracks) + } + } + const processedFiles = fileTracker.getTracks; + const playlists = playlistsTracker.getPlaylists; + const recentlyPlayedTracks = playbackStats.recentlyPlayedTracks; + if (processedFiles.length > 0) { + win.webContents.send('processedFiles', processedFiles); + win.webContents.send('userPlaylists', playlists); + win.webContents.send('recentlyPlayed', recentlyPlayedTracks); + win.webContents.send('playStats', playbackStats.getPlayStats); + } +} + +export async function resetApp() { + deleteFile(paths.filesTrackerLocation, true); + deleteFile(paths.playbackStatsLocation, true); + deleteFile(paths.playlistsLocation, true); + deleteFile(paths.settingsLocation, true); + sendNotificationToRenderer('Resetting FLB', 'Clearing all data', 'warning'); + setTimeout(() => { + app.quit(); + }, 3000); +} \ No newline at end of file diff --git a/src/main/modules/FilesTracker.ts b/src/main/modules/FilesTracker.ts index 9565a03..14f28d0 100644 --- a/src/main/modules/FilesTracker.ts +++ b/src/main/modules/FilesTracker.ts @@ -6,7 +6,7 @@ import { paths } from './Paths'; export class FilesTracker { private processedFiles: Array = []; - constructor () { + constructor() { if (fs.existsSync(paths.filesTrackerLocation)) { try { const data = JSON.parse( @@ -16,20 +16,23 @@ export class FilesTracker { this.checkForDeletedTracks(); } catch (error) { console.log('Error in reading the file tracker file'); + console.log(error); } } } - addFile (track: TrackType) { + addFile(track: TrackType) { this.processedFiles.unshift(track); } - updateFile (track: TrackType) { + addMultipleTracks(tracks: TrackType[]) { + this.processedFiles = [...this.processedFiles, ...tracks]; + } + updateFile(track: TrackType) { const index = this.processedFiles.findIndex( track => track.fileLocation === track.fileLocation ); this.processedFiles[index] = track; - this.saveChanges(); } - checkForDeletedTracks () { + checkForDeletedTracks() { console.log('Checking for deleted tracks'); const deletedTracks = this.processedFiles.filter( track => !fs.existsSync(track.fileLocation) @@ -38,27 +41,29 @@ export class FilesTracker { .map(track => track.fileLocation) .forEach(path => this.deleteFile(path)); } - deleteFile (pathToTrack: string) { + deleteFile(pathToTrack: string) { const indexOfDeletedTrack = this.processedFiles.findIndex( track => track.fileLocation === pathToTrack ); this.processedFiles.splice(indexOfDeletedTrack, 1); - this.saveChanges(); } - clearData () { + clearData() { this.processedFiles = []; } - saveChanges () { - fs.writeFile( - paths.filesTrackerLocation, - JSON.stringify(removeDuplicates(this.processedFiles, 'fileLocation')), - err => { - if (err) console.log(err); - } - ); + async saveChanges() { + try { + + fs.writeFileSync( + paths.filesTrackerLocation, + JSON.stringify(removeDuplicates(this.processedFiles, 'fileLocation')), + ); + } catch (err) { + console.log("Error saving file tracker"); + console.log(err); + } } - public get getTracks (): Array { + public get getTracks(): Array { return this.processedFiles; } } diff --git a/src/main/modules/UsageStatistics.ts b/src/main/modules/UsageStatistics.ts index ea2a0f2..9bad313 100644 --- a/src/main/modules/UsageStatistics.ts +++ b/src/main/modules/UsageStatistics.ts @@ -16,12 +16,12 @@ export class UsageManager { os_type: os.type(), app_launches: [] }; - constructor () { + constructor() { this.usageData.id = this.idGenerator(); this.usageData.app_launches = this.appLaunches(); this.addAppLaunch(); } - idGenerator () { + idGenerator() { if (fs.existsSync(paths.usageData)) { const data = JSON.parse( fs.readFileSync(paths.usageData, 'utf-8') @@ -31,7 +31,7 @@ export class UsageManager { return Math.round(Math.random() * 50000) + os.userInfo().username; } } - appLaunches () { + appLaunches() { if (fs.existsSync(paths.usageData)) { const data: UserInfo = JSON.parse( fs.readFileSync(paths.usageData, 'utf-8') @@ -41,11 +41,11 @@ export class UsageManager { return []; } } - addAppLaunch () { + addAppLaunch() { this.usageData.app_launches.push(Date()); this.saveUsageData(); } - saveUsageData () { + saveUsageData() { fs.writeFile( paths.usageData, JSON.stringify(this.usageData), @@ -54,8 +54,9 @@ export class UsageManager { } ); } - async sendUsageData () { - const supabase = createClient('https://tqfvneqybooaifvbdqlj.supabase.co', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYyNzMwODQ4OSwiZXhwIjoxOTQyODg0NDg5fQ.wr67SdghPIPEEhkoK7xeOHS3Dq2jojCYDYsD6ktUKTo'); + async sendUsageData() { + const key = process.env.SUPABASE_KEY || "" + const supabase = createClient('https://tqfvneqybooaifvbdqlj.supabase.co', key); try { const { data } = await supabase .from('Users') @@ -64,10 +65,11 @@ export class UsageManager { ], { upsert: true }); console.log(data); } catch (error) { - console.log('No internet'); + console.log('Unable to send stats'); + console.log(error); } } - public get getUsageData (): UserInfo { + public get getUsageData(): UserInfo { return this.usageData; } } diff --git a/src/main/reusables/messageToRenderer.ts b/src/main/reusables/messageToRenderer.ts new file mode 100644 index 0000000..7c03ea9 --- /dev/null +++ b/src/main/reusables/messageToRenderer.ts @@ -0,0 +1,13 @@ +import { win } from "@/background"; +import { NotificationTypes } from "@/types"; + +export function sendMessageToRenderer(listener: string, msg: any) { + win.webContents.send(listener, msg); +} + +export function sendNotificationToRenderer(title: string, subTitle: string = '', type: NotificationTypes = 'normal', isPersistent: boolean = false) { + const notification = { + title, subTitle, type, isPersistent + } + sendMessageToRenderer("notification", notification) +} diff --git a/src/main/utils/index.ts b/src/main/utils/index.ts index a0aa312..52bd1b7 100644 --- a/src/main/utils/index.ts +++ b/src/main/utils/index.ts @@ -4,11 +4,11 @@ import { DownloaderHelper } from 'node-downloader-helper'; import { ipcMain, Notification } from 'electron'; import isOnline from 'is-online'; -export function writeImageBuffer (imageBuffer: string, savePath: string) { +export function writeImageBuffer(imageBuffer: string, savePath: string) { fs.writeFileSync(savePath, imageBuffer); } -export async function downloadFile ( +export async function downloadFile( url: string, targetFolder: string, fileName: string @@ -22,11 +22,11 @@ export async function downloadFile ( dl.on('end', () => { resolve(dl.getDownloadPath()); }); - dl.on('error', () => reject('Error in downloading the cover')); + dl.on('error', () => console.log('Error in downloading the cover')); }); } -export function deleteFile (path: string, quiet: boolean) { +export function deleteFile(path: string, quiet: boolean) { if (fs.existsSync(path)) { fs.unlink(path.replace('file://', ''), err => { if (err) { @@ -44,7 +44,7 @@ export function deleteFile (path: string, quiet: boolean) { } } -export function extractTitleAndArtist (trackName: string): any { +export function extractTitleAndArtist(trackName: string): any { const split = trackName.split('-'); let artist; let title; @@ -67,15 +67,15 @@ export function extractTitleAndArtist (trackName: string): any { return { artist, title }; } -export function isValidFileType (path: string) { +export function isValidFileType(path: string) { return path.match(/\.mp3|\.webm|\.m4a|\.ogg/gi); } -export function removeMIME (str: string) { +export function removeMIME(str: string) { return str.replace(/(\.mp3)|(\.m4a)|(\.ogg)|(\.wav)/gi, ''); } -export function sendNativeNotification ( +export function sendNativeNotification( title: string, text: string, image: string @@ -95,7 +95,7 @@ export function sendNativeNotification ( }); } -export function sendMessageToRenderer (listener: string, msg: any) { +export function sendMessageToRenderer(listener: string, msg: any) { win.webContents.send(listener, msg); } diff --git a/src/renderer/assets/styles/themer.scss b/src/renderer/assets/styles/themer.scss index ea1a0b1..a08e6bf 100644 --- a/src/renderer/assets/styles/themer.scss +++ b/src/renderer/assets/styles/themer.scss @@ -76,13 +76,13 @@ html { --secondaryColor: rgb(246, 248, 250); --diplomaColor: rgb(250, 250, 250); --degreeColor: rgb(255, 255, 255); -} -.light_theme svg { - filter: invert(1); -} -input { - color: black !important; -} -.light_theme input::placeholder { - color: black !important; + input { + color: black !important; + } + svg { + filter: invert(1); + } + input::placeholder { + color: black !important; + } } diff --git a/src/renderer/components/local-music/side-pane/tag-editor.vue b/src/renderer/components/local-music/side-pane/tag-editor.vue index 5f39a82..a85397a 100644 --- a/src/renderer/components/local-music/side-pane/tag-editor.vue +++ b/src/renderer/components/local-music/side-pane/tag-editor.vue @@ -1,9 +1,7 @@