diff --git a/src/backend/src/playlist/playlist.controller.ts b/src/backend/src/playlist/playlist.controller.ts index 0ccb2ff..e7556bf 100644 --- a/src/backend/src/playlist/playlist.controller.ts +++ b/src/backend/src/playlist/playlist.controller.ts @@ -1,4 +1,4 @@ -import {Body, Controller, Delete, Get, Param, Post} from '@nestjs/common'; +import {Body, Controller, Delete, Get, Param, Post, Put} from '@nestjs/common'; import {PlaylistService} from "./playlist.service"; import {PlaylistModel} from "./playlist.model"; @@ -20,6 +20,11 @@ export class PlaylistController { await this.service.create(playlist); } + @Put(':id') + update(@Param('id') id: number, @Body() playlist: Partial): Promise { + return this.service.update(id, playlist); + } + @Delete(':id') remove(@Param('id') id: number): Promise { return this.service.remove(id); diff --git a/src/backend/src/playlist/playlist.entity.ts b/src/backend/src/playlist/playlist.entity.ts index 399862d..79f3eb1 100644 --- a/src/backend/src/playlist/playlist.entity.ts +++ b/src/backend/src/playlist/playlist.entity.ts @@ -16,6 +16,9 @@ export class PlaylistEntity { @Column({ nullable: true }) error?: string; + @Column({default: false}) + active?: boolean; + @Column({default: () => Date.now()}) createdAt?: number; diff --git a/src/backend/src/playlist/playlist.model.ts b/src/backend/src/playlist/playlist.model.ts index 64eb313..6fbd956 100644 --- a/src/backend/src/playlist/playlist.model.ts +++ b/src/backend/src/playlist/playlist.model.ts @@ -5,4 +5,5 @@ export interface PlaylistModel { tracks?: any[]; //todo fix it error?: string; createdAt?: number; + active?: boolean; } \ No newline at end of file diff --git a/src/backend/src/playlist/playlist.service.ts b/src/backend/src/playlist/playlist.service.ts index 8a62999..12353f2 100644 --- a/src/backend/src/playlist/playlist.service.ts +++ b/src/backend/src/playlist/playlist.service.ts @@ -10,6 +10,7 @@ import {resolve} from "path"; import {ConfigService} from "@nestjs/config"; import {EnviromentEnum} from "../enviroment.enum"; import * as fs from 'fs'; +import {Interval} from "@nestjs/schedule"; const fetch = require('isomorphic-unfetch'); const { getData, getPreview, getTracks, getDetails } = require('spotify-url-info')(fetch); @@ -26,8 +27,8 @@ export class PlaylistService { private readonly configService: ConfigService, ) {} - findAll(): Promise { - return this.repository.find({relations: {tracks: true}}); + findAll(relations: Record = {tracks: true}, where?: Partial): Promise { + return this.repository.find({where, relations}); } findOne(id: number): Promise { @@ -48,10 +49,8 @@ export class PlaylistService { } catch (err) { playlist2Save = {...playlist, error: String(err)}; } - const savedPlaylist = await this.repository.save(playlist2Save); - this.io.emit('playlistNew', savedPlaylist); - const folderName = resolve(__dirname, '..',this.configService.get(EnviromentEnum.DOWNLOADS_PATH), savedPlaylist.name); - !fs.existsSync(folderName) && fs.mkdirSync(folderName); + const savedPlaylist = await this.save(playlist2Save); + this.createPlaylistFolderStructure(savedPlaylist.name); for(let track of details?.tracks ?? []) { await this.trackService.create({ artist: track.artist, @@ -61,9 +60,16 @@ export class PlaylistService { } } - async update(id: number, playlist: PlaylistEntity): Promise { + async save(playlist: PlaylistEntity): Promise { + const savedPlaylist = await this.repository.save(playlist); + this.io.emit('playlistNew', savedPlaylist); + return savedPlaylist; + } + + async update(id: number, playlist: Partial): Promise { await this.repository.update(id, playlist); - this.io.emit('trackPlaylist', playlist); + const dbPlaylist = await this.findOne(id); + this.io.emit('playlistUpdate', dbPlaylist); } async retryFailedOfPlaylist(id: number): Promise { @@ -74,4 +80,34 @@ export class PlaylistService { } } } + + private createPlaylistFolderStructure(playlistName): void { + const folderName = resolve(__dirname, '..',this.configService.get(EnviromentEnum.DOWNLOADS_PATH), playlistName); + !fs.existsSync(folderName) && fs.mkdirSync(folderName); + } + + @Interval(3600000) + async checkActivePlaylists(): Promise { + const activePlaylists = await this.findAll({}, {active: true}); + for (let playlist of activePlaylists) { + let details; + try { + details = await getDetails(playlist.spotifyUrl); + } catch (err) { + await this.update(playlist.id, {...playlist, error: String(err)}); + } + this.createPlaylistFolderStructure(playlist.name); + for(let track of details?.tracks ?? []) { + const track2Save = { + artist: track.artist, + song: track.name, + spotifyUrl: track.previewUrl, + } + const isExist = !!(await this.trackService.findAll({...track2Save, playlist: {id: playlist.id}})).length; + if(!isExist) { + await this.trackService.create(track2Save, playlist); + } + } + } + } } \ No newline at end of file diff --git a/src/backend/src/track/track.service.ts b/src/backend/src/track/track.service.ts index e28f65e..0c61130 100644 --- a/src/backend/src/track/track.service.ts +++ b/src/backend/src/track/track.service.ts @@ -28,7 +28,7 @@ export class TrackService { private readonly configService: ConfigService, ) {} - findAll(where?: Partial, relations: string[] = []): Promise { + findAll(where?: {[key: string]: any}, relations: Record = {}): Promise { return this.repository.find({where, relations}); } @@ -82,7 +82,7 @@ export class TrackService { @Interval(1000) async download() { - const queuedTracks = await this.findAll({status: TrackStatusEnum.Queued}, ['playlist']); + const queuedTracks = await this.findAll({status: TrackStatusEnum.Queued}, {playlist: true}); for (let track of queuedTracks) { await this.update(track.id, {...track, status: TrackStatusEnum.Downloading}); } diff --git a/src/frontend/src/app/components/playlist-box/playlist-box.component.html b/src/frontend/src/app/components/playlist-box/playlist-box.component.html index 499aab3..46dd4b1 100644 --- a/src/frontend/src/app/components/playlist-box/playlist-box.component.html +++ b/src/frontend/src/app/components/playlist-box/playlist-box.component.html @@ -16,6 +16,11 @@ +       {{trackCompletedCount$ | async}}/{{trackCount$ | async}}  diff --git a/src/frontend/src/app/components/playlist-box/playlist-box.component.ts b/src/frontend/src/app/components/playlist-box/playlist-box.component.ts index d930575..b91c601 100644 --- a/src/frontend/src/app/components/playlist-box/playlist-box.component.ts +++ b/src/frontend/src/app/components/playlist-box/playlist-box.component.ts @@ -57,4 +57,8 @@ export class PlaylistBoxComponent { retryFailed(id: number): void { this.service.retryFailed(id); } + + setActive(id: number, currentActive: boolean = false): void { + this.service.setActive(id, !currentActive) + } } diff --git a/src/frontend/src/app/services/playlist.service.ts b/src/frontend/src/app/services/playlist.service.ts index d477412..02418b5 100644 --- a/src/frontend/src/app/services/playlist.service.ts +++ b/src/frontend/src/app/services/playlist.service.ts @@ -26,6 +26,7 @@ export interface Playlist { name?: string; spotifyUrl: string; error?: string; + active: boolean; createdAt: number; } @@ -55,7 +56,7 @@ export class PlaylistService { all$ = this.store.combine({ entities: this.store.pipe(selectAllEntities()), UIEntities: this.store.pipe(selectEntities({ ref: UIEntitiesRef })), - }).pipe(unionEntities(), map(data => data.sort((a, b) => b.createdAt - a.createdAt))); + }).pipe(unionEntities(), map(data => data.sort((a, b) => this.sort(a, b)))); loading$ = this.store.pipe(joinRequestResult([STORE_NAME])); createLoading$ = this.store.pipe(joinRequestResult([CREATE_LOADING], { initialStatus: 'idle' })); @@ -100,7 +101,7 @@ export class PlaylistService { if (playlist?.error || errorCount === trackCount) { return PlaylistStatusEnum.Error; } else if (trackCount === completedCount) { - return PlaylistStatusEnum.Completed; + return playlist?.active ? PlaylistStatusEnum.Subscribed : PlaylistStatusEnum.Completed; } else if (errorCount > 1) { return PlaylistStatusEnum.Warning; } @@ -157,4 +158,12 @@ export class PlaylistService { retryFailed(id: number): void { this.http.get(`${ENDPOINT}/retry/${id}`).subscribe(); } + + setActive(id: number, active: boolean): void { + this.http.put(`${ENDPOINT}/${id}`, {active}).subscribe(); + } + + private sort(a: Playlist & PlaylistUi, b: Playlist & PlaylistUi): number { + return a.active === b.active ? (b.createdAt - a.createdAt) : (a.active < b.active ? 1 : -1); + } }