git.iko.soy/maciek134/podphoenix
on main help sign in
Backported clean up settings patch (rev 41)
d839119Nekhelesh Ramananthan
app/podbird.qml+23−2
@@ -127,14 +127,35 @@ MainView {
127
127
128 SingleDownload {
128 SingleDownload {
129 id: imageDownloader
129 id: imageDownloader
130+
130− property string feed;
131+ property string feed
132+ property var queue: []
133+ property int queueLength: 0
134+
131 onFinished: {
135 onFinished: {
132 var db = Podcasts.init();
136 var db = Podcasts.init();
133 var finalLocation = fileManager.saveDownload(path);
137 var finalLocation = fileManager.saveDownload(path);
134 db.transaction(function (tx) {
138 db.transaction(function (tx) {
135− tx.executeSql("UPDATE Podcast SET image=? WHERE feed=?", [finalLocation, feed]);
139+ tx.executeSql("UPDATE Podcast SET image=? WHERE feed=?", [finalLocation, imageDownloader.feed]);
140+ queue.shift();
141+ queueLength--
142+ if (queue.length > 0) {
143+ imageDownloader.feed = queue[0][0];
144+ download(queue[0][1]);
145+ } else {
146+ feed = "";
147+ }
136 });
148 });
137 }
149 }
150+
151+ function addDownload(feed, url) {
152+ queue.push([feed, url]);
153+ queueLength++
154+ if (queue.length == 1) {
155+ imageDownloader.feed = feed;
156+ download(url);
157+ }
158+ }
138 }
159 }
139
160
140 Component {
161 Component {
app/ui/SearchPage.qml+2−4
@@ -276,8 +276,7 @@ Page {
276 onClicked: {
276 onClicked: {
277 if (!model.subscribed) {
277 if (!model.subscribed) {
278 Podcasts.subscribe(model.artist, model.name, model.feed, model.image);
278 Podcasts.subscribe(model.artist, model.name, model.feed, model.image);
279− imageDownloader.feed = model.feed;
279+ imageDownloader.addDownload(model.feed, model.image)
280− imageDownloader.download(model.image);
281 } else {
280 } else {
282 var db = Podcasts.init();
281 var db = Podcasts.init();
283 db.transaction(function (tx) {
282 db.transaction(function (tx) {
@@ -440,8 +439,7 @@ Page {
440
439
441 if(name != "") {
440 if(name != "") {
442 Podcasts.subscribe(artist, name, feed, image);
441 Podcasts.subscribe(artist, name, feed, image);
443− imageDownloader.feed = feed;
442+ imageDownloader.addDownload(feed, image)
444− imageDownloader.download(image);
445 tabs.selectedTabIndex = 2;
443 tabs.selectedTabIndex = 2;
446 } else {
444 } else {
447 PopupUtils.open(subscribeFailedDialog);
445 PopupUtils.open(subscribeFailedDialog);
app/ui/SettingsPage.qml+189
@@ -19,7 +19,9 @@
19import QtQuick 2.4
19import QtQuick 2.4
20import Ubuntu.Components 1.3
20import Ubuntu.Components 1.3
21import Ubuntu.Components.Popups 1.3
21import Ubuntu.Components.Popups 1.3
22+import QtQuick.LocalStorage 2.0
22import "../components"
23import "../components"
24+import "../podcasts.js" as Podcasts
23
25
24Page {
26Page {
25 id: settingsPage
27 id: settingsPage
@@ -210,6 +212,103 @@ Page {
210 onClicked: mainStack.push(Qt.resolvedUrl("../settings/DownloadSetting.qml"))
212 onClicked: mainStack.push(Qt.resolvedUrl("../settings/DownloadSetting.qml"))
211 }
213 }
212
214
215+ ListItem {
216+ id: refreshArtListItem
217+
218+ property int numberOfPodcasts: 0
219+ property int queueLength: imageDownloader.queueLength
220+
221+ ListItemLayout {
222+ id: refreshArt
223+ title.text: i18n.tr("Refresh podcast artwork")
224+ title.color: podbird.appTheme.baseText
225+ summary.text: i18n.tr("Update all podcasts artwork and fix missing ones (this only works with podcasts added via iTunes® search)")
226+ summary.color: podbird.appTheme.baseSubText
227+ summary.maximumLineCount: 3
228+ ProgressionSlot{}
229+ }
230+
231+ CustomProgressBar {
232+ id: progressBar
233+ anchors { top: refreshArt.bottom; left: parent.left; right: parent.right; leftMargin: units.gu(2); rightMargin: units.gu(2) }
234+ progress: (refreshArtListItem.numberOfPodcasts - refreshArtListItem.queueLength) * (100 / refreshArtListItem.numberOfPodcasts)
235+ height: progress > 0 && progress < 100 ? units.dp(5) : 0
236+ }
237+
238+ divider.visible: false
239+ height: refreshArt.height + progressBar.height
240+ onClicked: {
241+ var db = Podcasts.init()
242+ db.transaction(function (tx) {
243+ var rs = tx.executeSql("SELECT rowid, * FROM Podcast ORDER BY name ASC");
244+ refreshArtListItem.numberOfPodcasts = rs.rows.length
245+ for (var i=0; i < rs.rows.length; i++) {
246+ var podcast = rs.rows.item(i);
247+ settingsPage.getPodcastCoverArt(podcast.name, podcast.feed, podcast.image)
248+ }
249+ });
250+ }
251+ }
252+
253+ HeaderListItem {
254+ title.text: i18n.tr("Storage Settings")
255+ }
256+
257+ ListItem {
258+ id: orphanListItem
259+
260+ property int fileCount: 0
261+ property int linkCount: 0
262+
263+ ListItemLayout {
264+ id: orphanLayout
265+ title.text: i18n.tr("Delete orphaned files and links")
266+ title.color: podbird.appTheme.baseText
267+ summary.text: i18n.tr("Free space by removing orphaned downloaded files and links")
268+ summary.color: podbird.appTheme.baseSubText
269+ ProgressionSlot {}
270+ }
271+
272+ CustomProgressBar {
273+ id: orphanProgressBar
274+ progress: 200
275+ anchors { top: orphanLayout.bottom; left: parent.left; right: parent.right; leftMargin: units.gu(2); rightMargin: units.gu(2) }
276+ height: indeterminateProgress ? units.dp(5) : 0
277+ }
278+
279+ divider.visible: false
280+ height: orphanLayout.height + orphanProgressBar.height
281+ onClicked: {
282+ orphanProgressBar.indeterminateProgress = true
283+ settingsPage.removeOrphans()
284+ orphanProgressBar.indeterminateProgress = false
285+ var popup = PopupUtils.open(cleanUpDialog, settingsPage);
286+ popup.orphanCount = orphanListItem.fileCount + orphanListItem.linkCount
287+ }
288+ }
289+
290+ Component {
291+ id: cleanUpDialog
292+ Dialog {
293+ id: dialogInternal
294+
295+ property int orphanCount: 0
296+
297+ title: orphanCount > 0 ? i18n.tr("Removed Orphaned files and links") : i18n.tr("No Orphans found!")
298+ text: orphanCount > 0 ? i18n.tr("All orphaned files have been deleted to recover disk space. Orphaned links \
299+pointing at invalid files have also been cleaned up.")
300+ : i18n.tr("No orphaned files have been found to recover disk space. Podbird database is clean.")
301+
302+ Button {
303+ text: i18n.tr("Close")
304+ color: podbird.appTheme.positiveActionButton
305+ onClicked: {
306+ PopupUtils.close(dialogInternal)
307+ }
308+ }
309+ }
310+ }
311+
213 HeaderListItem {
312 HeaderListItem {
214 // TRANSLATORS: Shortened form of "Miscellaneous" which is shown to denote other setting options
313 // TRANSLATORS: Shortened form of "Miscellaneous" which is shown to denote other setting options
215 // that doesn't fit into any other category.
314 // that doesn't fit into any other category.
@@ -238,6 +337,96 @@ Page {
238 }
337 }
239 }
338 }
240 }
339 }
340+
341+ function getPodcastCoverArt(name, feedUrl, image) {
342+ var coverArt
343+ var url = "https://itunes.apple.com/search?term=" + name + "&media=podcast&entity=podcast"
344+ var xhr = new XMLHttpRequest;
345+ xhr.open("GET", url);
346+ xhr.onreadystatechange = function() {
347+ if (xhr.readyState === XMLHttpRequest.DONE) {
348+ var json = JSON.parse(xhr.responseText);
349+ var db = Podcasts.init();
350+
351+ for(var i in json.results) {
352+ if (name == json.results[i].trackName) {
353+ if (feedUrl == json.results[i].feedUrl) {
354+ fileManager.deleteFile(image);
355+ coverArt = json.results[i].artworkUrl600
356+ if (coverArt) {
357+ imageDownloader.addDownload(feedUrl, coverArt)
358+ }
359+ }
360+ }
361+ }
362+ }
363+ }
364+ xhr.send();
365+ }
366+
367+ function removeOrphans() {
368+ var filelist = fileManager.getDownloadedEpisodes()
369+
370+ orphanListItem.fileCount = 0
371+ orphanListItem.linkCount = 0
372+
373+ var db = Podcasts.init()
374+ db.transaction(function (tx) {
375+ var rs = tx.executeSql("SELECT rowid, * FROM Podcast ORDER BY name ASC");
376+
377+ // Go through every downloaded file and check if they are orphaned
378+ for (var l = 0; l < filelist.length; l++) {
379+ var isOrphanFile = true
380+
381+ // Go through every podcast
382+ for (var i=0; i < rs.rows.length; i++) {
383+ var podcast = rs.rows.item(i);
384+ var rs2 = tx.executeSql("SELECT rowid, * FROM Episode WHERE podcast=?", [podcast.rowid]);
385+
386+ // Go through every episode in the podcast
387+ for (var j=0; j < rs2.rows.length; j++) {
388+ var episode = rs2.rows.item(j);
389+ if (episode.downloadedfile) {
390+ if(fileManager.podcastDirectory + "/" + filelist[l] === episode.downloadedfile) {
391+ isOrphanFile = false
392+ }
393+ }
394+ }
395+ }
396+
397+ if (isOrphanFile) {
398+ orphanListItem.fileCount++
399+ fileManager.deleteFile(fileManager.podcastDirectory + "/" + filelist[l])
400+ console.log("[LOG]: Removed Orphan File: " + fileManager.podcastDirectory + "/" + filelist[l])
401+ }
402+ }
403+
404+ // Go through every podcast in the db and find orphaned links
405+ for (i=0; i < rs.rows.length; i++) {
406+ var isOrphanLink = true
407+ podcast = rs.rows.item(i);
408+ rs2 = tx.executeSql("SELECT rowid, * FROM Episode WHERE podcast=?", [podcast.rowid]);
409+
410+ // Go through every episode in the podcast
411+ for (j=0; j < rs2.rows.length; j++) {
412+ episode = rs2.rows.item(j);
413+ if (episode.downloadedfile) {
414+ for (l = 0; l < filelist.length; l++) {
415+ if(episode.downloadedfile === fileManager.podcastDirectory + "/" + filelist[l]) {
416+ isOrphanLink = false
417+ }
418+ }
419+
420+ if (isOrphanLink) {
421+ orphanListItem.linkCount++
422+ tx.executeSql("UPDATE Episode SET downloadedfile = NULL WHERE guid = ?", [episode.guid]);
423+ console.log("[LOG]: Removed Orphan Link: " + episode.downloadedfile)
424+ }
425+ }
426+ }
427+ }
428+ });
429+ }
241}
430}
242
431
243
432
backend/modules/Podbird/filemanager.cpp+18−5
@@ -24,7 +24,8 @@
24#include "filemanager.h"
24#include "filemanager.h"
25
25
26FileManager::FileManager(QObject *parent):
26FileManager::FileManager(QObject *parent):
27− QObject(parent)
27+ QObject(parent),
28+ m_podcastDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QDir::separator() + "podcasts")
28{
29{
29 // Remove any downloads that haven't been collected previously (because we were closed before they finished)
30 // Remove any downloads that haven't been collected previously (because we were closed before they finished)
30 QString downloadManagerPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + "ubuntu-download-manager" + QDir::separator() + "com.mikeasoft.podbird";
31 QString downloadManagerPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + "ubuntu-download-manager" + QDir::separator() + "com.mikeasoft.podbird";
@@ -38,6 +39,11 @@ FileManager::~FileManager() {
38
39
39}
40}
40
41
42+QString FileManager::podcastDirectory() const
43+{
44+ return m_podcastDir;
45+}
46+
41void FileManager::deleteFile(QString path) {
47void FileManager::deleteFile(QString path) {
42 QFile file(path);
48 QFile file(path);
43 if (file.exists()) {
49 if (file.exists()) {
@@ -46,17 +52,16 @@ void FileManager::deleteFile(QString path) {
46}
52}
47
53
48QString FileManager::saveDownload(QString origPath) {
54QString FileManager::saveDownload(QString origPath) {
49− QString destination = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QDir::separator() + "podcasts";
50− QDir destDir(destination);
55+ QDir destDir(m_podcastDir);
51 if(!destDir.exists()) {
56 if(!destDir.exists()) {
52− destDir.mkpath(destination);
57+ destDir.mkpath(m_podcastDir);
53 }
58 }
54 QFileInfo fi(origPath);
59 QFileInfo fi(origPath);
55 QFile *destFile;
60 QFile *destFile;
56 QString filePath;
61 QString filePath;
57 int attempts = 0;
62 int attempts = 0;
58 do {
63 do {
59− filePath = destination + QDir::separator() + fi.fileName();
64+ filePath = m_podcastDir + QDir::separator() + fi.fileName();
60 if (attempts > 0) {
65 if (attempts > 0) {
61 filePath += "." + QString::number(attempts);
66 filePath += "." + QString::number(attempts);
62 }
67 }
@@ -66,3 +71,11 @@ QString FileManager::saveDownload(QString origPath) {
66 QFile::rename(origPath, filePath);
71 QFile::rename(origPath, filePath);
67 return filePath;
72 return filePath;
68}
73}
74+
75+QStringList FileManager::getDownloadedEpisodes() {
76+ QDir destDir(m_podcastDir);
77+ QStringList filters;
78+ filters << "*.mp3" << "*.mp3.*" << "*.m4a" << "*.m4a.*" << "*.ogg" << "*.ogg.*" << "*.oga" << "*.oga.*" << "*.wma";
79+ destDir.setNameFilters(filters);
80+ return destDir.entryList();
81+}
backend/modules/Podbird/filemanager.h+11
@@ -25,13 +25,24 @@ class FileManager : public QObject
25{
25{
26 Q_OBJECT
26 Q_OBJECT
27
27
28+ // READONLY Property to return the podcast directory path
29+ Q_PROPERTY( QString podcastDirectory
30+ READ podcastDirectory
31+ CONSTANT)
32+
28public:
33public:
29 explicit FileManager(QObject *parent = 0);
34 explicit FileManager(QObject *parent = 0);
30 ~FileManager();
35 ~FileManager();
31
36
37+ QString podcastDirectory() const;
38+
32public:
39public:
33 Q_INVOKABLE void deleteFile(QString path);
40 Q_INVOKABLE void deleteFile(QString path);
34 Q_INVOKABLE QString saveDownload(QString path);
41 Q_INVOKABLE QString saveDownload(QString path);
42+ Q_INVOKABLE QStringList getDownloadedEpisodes();
43+
44+private:
45+ QString m_podcastDir;
35};
46};
36
47
37#endif // FILEMANAGER_H
48#endif // FILEMANAGER_H