From 881a103051dbc2dcece52caabc5db8ee78e70666 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Sat, 7 Mar 2020 17:00:50 -0500 Subject: [PATCH] Added duration of video in subscription file card along with implementations of deleting subscribed videos. Subscribed videos now get reloaded after deletion sidenav now closes when navigating Updated subscription info to include more info --- backend/app.js | 32 +++++-- backend/subscriptions.js | 85 +++++++++++++++++-- src/app/app.component.html | 4 +- .../subscription-info-dialog.component.html | 17 +++- .../subscription-info-dialog.component.scss | 7 ++ src/app/posts.services.ts | 4 + .../subscription-file-card.component.html | 8 +- .../subscription-file-card.component.scss | 9 +- .../subscription-file-card.component.ts | 44 +++++++++- .../subscription/subscription.component.html | 2 +- .../subscriptions.component.html | 2 +- 11 files changed, 191 insertions(+), 23 deletions(-) diff --git a/backend/app.js b/backend/app.js index dea5e47..2a4ac56 100644 --- a/backend/app.js +++ b/backend/app.js @@ -160,7 +160,11 @@ async function loadConfig() { // get subscriptions if (allowSubscriptions) { + // runs initially, then runs every ${subscriptionCheckInterval} seconds watchSubscriptions(); + setInterval(() => { + watchSubscriptions(); + }, subscriptionsCheckInterval * 1000); } // start the server here @@ -190,9 +194,7 @@ function watchSubscriptions() { let sub = subscriptions[i]; console.log('watching ' + sub.name + ' with delay interval of ' + delay_interval); setTimeout(() => { - setInterval(() => { - subscriptions_api.getVideosForSub(sub); - }, subscriptionsCheckInterval * 1000); + subscriptions_api.getVideosForSub(sub); }, current_delay); current_delay += delay_interval; if (current_delay >= subscriptionsCheckInterval * 1000) current_delay = 0; @@ -415,10 +417,11 @@ function deleteAudioFile(name) { }); } -async function deleteVideoFile(name) { +async function deleteVideoFile(name, customPath = null) { return new Promise(resolve => { - var jsonPath = path.join(videoFolderPath,name+'.info.json'); - var videoFilePath = path.join(videoFolderPath,name+'.mp4'); + let filePath = customPath ? customPath : videoFolderPath; + var jsonPath = path.join(filePath,name+'.info.json'); + var videoFilePath = path.join(filePath,name+'.mp4'); jsonPath = path.join(__dirname, jsonPath); videoFilePath = path.join(__dirname, videoFilePath); @@ -910,6 +913,23 @@ app.post('/api/unsubscribe', async (req, res) => { } }); +app.post('/api/deleteSubscriptionFile', async (req, res) => { + let deleteForever = req.body.deleteForever; + let file = req.body.file; + let sub = req.body.sub; + + let success = await subscriptions_api.deleteSubscriptionFile(sub, file, deleteForever); + + if (success) { + res.send({ + success: success + }); + } else { + res.sendStatus(500); + } + +}); + app.post('/api/getSubscription', async (req, res) => { let subID = req.body.id; diff --git a/backend/subscriptions.js b/backend/subscriptions.js index c9d3cc6..d185365 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -64,6 +64,51 @@ async function unsubscribe(sub, deleteMode) { } +async function deleteSubscriptionFile(sub, file, deleteForever) { + const basePath = config_api.getConfigItem('ytdl_subscriptions_base_path'); + const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive'); + const appendedBasePath = getAppendedBasePath(sub, basePath); + const name = file; + let retrievedID = null; + return new Promise(resolve => { + let filePath = appendedBasePath; + var jsonPath = path.join(filePath,name+'.info.json'); + var videoFilePath = path.join(filePath,name+'.mp4'); + jsonPath = path.join(__dirname, jsonPath); + videoFilePath = path.join(__dirname, videoFilePath); + + jsonExists = fs.existsSync(jsonPath); + videoFileExists = fs.existsSync(videoFilePath); + + if (jsonExists) { + retrievedID = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))['id']; + fs.unlinkSync(jsonPath); + } + + if (videoFileExists) { + fs.unlink(videoFilePath, function(err) { + if (fs.existsSync(jsonPath) || fs.existsSync(videoFilePath)) { + resolve(false); + } else { + // check if the user wants the video to be redownloaded (deleteForever === false) + if (!deleteForever && useArchive && sub.archive && retrievedID) { + const archive_path = path.join(sub.archive, 'archive.txt') + // if archive exists, remove line with video ID + if (fs.existsSync(archive_path)) { + removeIDFromArchive(archive_path, retrievedID); + } + } + resolve(true); + } + }); + } else { + // TODO: tell user that the file didn't exist + resolve(true); + } + + }); +} + async function getVideosForSub(sub) { return new Promise(resolve => { const basePath = config_api.getConfigItem('ytdl_subscriptions_base_path'); @@ -199,10 +244,38 @@ const deleteFolderRecursive = function(folder_to_delete) { } }; -module.exports = { - getSubscription : getSubscription, - getAllSubscriptions: getAllSubscriptions, - subscribe : subscribe, - unsubscribe : unsubscribe, - getVideosForSub : getVideosForSub +function removeIDFromArchive(archive_path, id) { + fs.readFile(archive_path, {encoding: 'utf-8'}, function(err, data) { + if (err) throw error; + + let dataArray = data.split('\n'); // convert file data in an array + const searchKeyword = id; // we are looking for a line, contains, key word id in the file + let lastIndex = -1; // let say, we have not found the keyword + + for (let index=0; index { + if (err) throw err; + // console.log ('Successfully updated the file data'); + }); + + }); +} + +module.exports = { + getSubscription : getSubscription, + getAllSubscriptions : getAllSubscriptions, + subscribe : subscribe, + unsubscribe : unsubscribe, + deleteSubscriptionFile : deleteSubscriptionFile, + getVideosForSub : getVideosForSub } diff --git a/src/app/app.component.html b/src/app/app.component.html index 0eb8d77..057f745 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -30,8 +30,8 @@ - Home - Subscriptions + Home + Subscriptions diff --git a/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html b/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html index b0ccf22..fb967e2 100644 --- a/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html +++ b/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html @@ -1,7 +1,22 @@

{{sub.name}}

- Type: {{(sub.isPlaylist ? 'Playlist' : 'Channel')}} +
+ Type: + {{(sub.isPlaylist ? 'Playlist' : 'Channel')}} +
+
+ URL: + {{sub.url}} +
+
+ ID: + {{sub.id}} +
+
+ Archive: + {{sub.archive}} +
diff --git a/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.scss b/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.scss index e69de29..9feea64 100644 --- a/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.scss +++ b/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.scss @@ -0,0 +1,7 @@ +.info-item { + margin-bottom: 12px; +} + +.info-item-value { + font-size: 13px; +} \ No newline at end of file diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index baf9ab2..e769916 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -149,6 +149,10 @@ export class PostsService { return this.http.post(this.path + 'unsubscribe', {sub: sub, deleteMode: deleteMode}) } + deleteSubscriptionFile(sub, file, deleteForever) { + return this.http.post(this.path + 'deleteSubscriptionFile', {sub: sub, file: file, deleteForever: deleteForever}) + } + getSubscription(id) { return this.http.post(this.path + 'getSubscription', {id: id}); } diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.html b/src/app/subscription/subscription-file-card/subscription-file-card.component.html index da0a6ca..246ea91 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.html +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.html @@ -1,9 +1,11 @@
+
+ Length: {{formattedDuration}} +
- - - + +
diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.scss b/src/app/subscription/subscription-file-card/subscription-file-card.component.scss index a0e5f36..65cd869 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.scss +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.scss @@ -55,6 +55,13 @@ bottom: 5px; position: absolute; } + + .duration-time { + position: absolute; + left: 5px; + top: 5px; + z-index: 99999; + } @media (max-width: 576px){ @@ -66,4 +73,4 @@ width: 175px; } - } \ No newline at end of file + } diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts index d17baa2..7a00bc5 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { MatSnackBar } from '@angular/material'; import { Router } from '@angular/router'; +import { PostsService } from 'app/posts.services'; @Component({ selector: 'app-subscription-file-card', @@ -15,11 +16,15 @@ export class SubscriptionFileCardComponent implements OnInit { scrollSubject; scrollAndLoad; + formattedDuration = null; + @Input() file; + @Input() sub; @Output() goToFileEmit = new EventEmitter(); + @Output() reloadSubscription = new EventEmitter(); - constructor(private snackBar: MatSnackBar) { + constructor(private snackBar: MatSnackBar, private postsService: PostsService) { this.scrollSubject = new Subject(); this.scrollAndLoad = Observable.merge( Observable.fromEvent(window, 'scroll'), @@ -28,7 +33,9 @@ export class SubscriptionFileCardComponent implements OnInit { } ngOnInit() { - + if (this.file.duration) { + this.formattedDuration = fancyTimeFormat(this.file.duration); + } } onImgError(event) { @@ -47,6 +54,20 @@ export class SubscriptionFileCardComponent implements OnInit { this.goToFileEmit.emit(this.file.title); } + deleteAndRedownload() { + this.postsService.deleteSubscriptionFile(this.sub, this.file.id, false).subscribe(res => { + this.reloadSubscription.emit(true); + this.openSnackBar(`Successfully deleted file: '${this.file.id}'`, 'Dismiss.'); + }); + } + + deleteForever() { + this.postsService.deleteSubscriptionFile(this.sub, this.file.id, true).subscribe(res => { + this.reloadSubscription.emit(true); + this.openSnackBar(`Successfully deleted file: '${this.file.id}'`, 'Dismiss.'); + }); + } + public openSnackBar(message: string, action: string) { this.snackBar.open(message, action, { duration: 2000, @@ -54,3 +75,22 @@ export class SubscriptionFileCardComponent implements OnInit { } } + +function fancyTimeFormat(time) +{ + // Hours, minutes and seconds + const hrs = ~~(time / 3600); + const mins = ~~((time % 3600) / 60); + const secs = ~~time % 60; + + // Output like "1:01" or "4:03:59" or "123:03:59" + let ret = ''; + + if (hrs > 0) { + ret += '' + hrs + ':' + (mins < 10 ? '0' : ''); + } + + ret += '' + mins + ':' + (secs < 10 ? '0' : ''); + ret += '' + secs; + return ret; +} \ No newline at end of file diff --git a/src/app/subscription/subscription/subscription.component.html b/src/app/subscription/subscription/subscription.component.html index 9fa386b..8b6c635 100644 --- a/src/app/subscription/subscription/subscription.component.html +++ b/src/app/subscription/subscription/subscription.component.html @@ -13,7 +13,7 @@
- +
diff --git a/src/app/subscriptions/subscriptions.component.html b/src/app/subscriptions/subscriptions.component.html index 85ddb6b..80572fd 100644 --- a/src/app/subscriptions/subscriptions.component.html +++ b/src/app/subscriptions/subscriptions.component.html @@ -27,7 +27,7 @@

You have no channel subscriptions.

-

Playlists

+

Playlists