git.iko.soy/maciek134/podphoenix
on main help sign in
* Added icons to the tab menu

* Do not hide the currently viewed tab from the tabs list as it breaks muscle memory and was found to be annoying to the user * Added settings option to refresh podcast artwork (works only for podcasts added via iTunes search) * Added settings option to clean up orphaned files and links * Fixed theme warning messages shown at app startup * Fixed captilization and direct users to the correct page for adding podcasts (string change)

commit040ff439b9c0d123a1f47a92609e99b17089e16e authorNekhelesh Ramananthan <krnekhelesh@gmail.com> dateSun 11 Dec 2016, 21:13 (10 years ago) parent9beed258f225f7
12 files changed+361−65
app/components/TabsList.qml+8−4 app/graphics/podcast-symbol.png app/podbird.qml+25−5 app/ui/EpisodesPage.qml+2−2 app/ui/EpisodesTab.qml+1−1 app/ui/PodcastsTab.qml+1−1 app/ui/Queue.qml+2−2 app/ui/SearchPage.qml+3−5 app/ui/SettingsPage.qml+214 backend/modules/Podbird/filemanager.cpp+18−5 backend/modules/Podbird/filemanager.h+11 po/com.mikeasoft.podbird.pot+76−40
app/components/TabsList.qml+8−4
@@ -24,32 +24,36 @@ ActionList {
2424
2525 children: [
2626 Action {
27+ iconName: "slideshow"
2728 text: i18n.tr("Episodes")
28− visible: tabs.selectedTab !== episodesTab
29+ enabled: tabs.selectedTab !== episodesTab
2930 onTriggered: {
3031 tabs.selectedTabIndex = 0
3132 }
3233 },
3334
3435 Action {
36+ iconName: "add"
3537 text: i18n.tr("Add New Podcasts")
36− visible: tabs.selectedTab !== searchTab
38+ enabled: tabs.selectedTab !== searchTab
3739 onTriggered: {
3840 tabs.selectedTabIndex = 1
3941 }
4042 },
4143
4244 Action {
45+ iconSource: Qt.resolvedUrl("../graphics/podcast-symbol.png")
4346 text: i18n.tr("Podcasts")
44− visible: tabs.selectedTab !== podcastTab
47+ enabled: tabs.selectedTab !== podcastTab
4548 onTriggered: {
4649 tabs.selectedTabIndex = 2
4750 }
4851 },
4952
5053 Action {
54+ iconName: "settings"
5155 text: i18n.tr("Settings")
52− visible: tabs.selectedTab !== settingsTab
56+ enabled: tabs.selectedTab !== settingsTab
5357 onTriggered: {
5458 tabs.selectedTabIndex = 3
5559 }
app/graphics/podcast-symbol.pngnew file

Binary file not shown.

app/podbird.qml+25−5
@@ -98,9 +98,8 @@ MainView {
9898 id: themeManager
9999 source: settings.themeName
100100 onSourceChanged: {
101− theme.palette.normal.backgroundText = UbuntuColors.lightGrey
102− theme.name = settings.themeName == "Dark.qml" ? "Ubuntu.Components.Themes.SuruDark"
103− : "Ubuntu.Components.Themes.Ambiance"
101+ podbird.theme.name = settings.themeName == "Dark.qml" ? "Ubuntu.Components.Themes.SuruDark"
102+ : "Ubuntu.Components.Themes.Ambiance"
104103 }
105104 }
106105
@@ -127,14 +126,35 @@ MainView {
127126
128127 SingleDownload {
129128 id: imageDownloader
130− property string feed;
129+
130+ property string feed
131+ property var queue: []
132+ property int queueLength: 0
133+
131134 onFinished: {
132135 var db = Podcasts.init();
133136 var finalLocation = fileManager.saveDownload(path);
134137 db.transaction(function (tx) {
135− tx.executeSql("UPDATE Podcast SET image=? WHERE feed=?", [finalLocation, feed]);
138+ tx.executeSql("UPDATE Podcast SET image=? WHERE feed=?", [finalLocation, imageDownloader.feed]);
139+ queue.shift();
140+ queueLength--
141+ if (queue.length > 0) {
142+ imageDownloader.feed = queue[0][0];
143+ download(queue[0][1]);
144+ } else {
145+ feed = "";
146+ }
136147 });
137148 }
149+
150+ function addDownload(feed, url) {
151+ queue.push([feed, url]);
152+ queueLength++
153+ if (queue.length == 1) {
154+ imageDownloader.feed = feed;
155+ download(url);
156+ }
157+ }
138158 }
139159
140160 Component {
app/ui/EpisodesPage.qml+2−2
@@ -569,8 +569,8 @@ Page {
569569 title.text: model.name !== undefined ? model.name.trim() : "Undefined"
570570 title.color: downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText
571571 : podbird.appTheme.baseText
572− // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800
573− title.maximumLineCount: 1
572+ title.wrapMode: Text.WordWrap
573+ title.maximumLineCount: 2
574574
575575 subtitle.text: model.duration === 0 || model.duration === undefined ? model.downloadedfile ? "📎 " + Qt.formatDate(new Date(model.published), "MMM d, yyyy")
576576 : Qt.formatDate(new Date(model.published), "MMM d, yyyy")
app/ui/EpisodesTab.qml+1−1
@@ -539,7 +539,7 @@ Tab {
539539 title.text: model.name !== undefined ? model.name.trim() : "Undefined"
540540 title.color: downloader.downloadingGuid === model.guid ? podbird.appTheme.focusText
541541 : podbird.appTheme.baseText
542− // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800
542+ title.wrapMode: Text.WordWrap
543543 title.maximumLineCount: 1
544544
545545 subtitle.text: model.duration === 0 || model.duration === undefined ? model.downloadedfile ? "📎 " + model.artist
app/ui/PodcastsTab.qml+1−1
@@ -125,7 +125,7 @@ Page {
125125 EmptyState {
126126 icon.source: podcastModel.count === 0 ? Qt.resolvedUrl("../graphics/owlSearch.svg") : Qt.resolvedUrl("../graphics/notFound.svg")
127127 title: podcastModel.count === 0 ? i18n.tr("No Podcast Subscriptions") : i18n.tr("No Podcasts Found")
128− subTitle: podcastModel.count === 0 ? i18n.tr("You haven't subscribed to any podcasts yet, visit the 'Find New Podcasts' page to add some.")
128+ subTitle: podcastModel.count === 0 ? i18n.tr("You haven't subscribed to any podcasts yet, visit the 'Add New Podcasts' page to add some.")
129129 : i18n.tr("No podcasts found matching the search term.")
130130 }
131131 }
app/ui/Queue.qml+2−2
@@ -56,8 +56,8 @@ ListView {
5656 }
5757
5858 title.text: layout.metaModel.name
59− // #FIXME: Change this 2 to prevent title eliding when UITK is updated to rev > 1800
60− title.maximumLineCount: 1
59+ title.wrapMode: Text.WordWrap
60+ title.maximumLineCount: 2
6161 title.color: player.playlist.currentIndex === index ? podbird.appTheme.focusText
6262 : podbird.appTheme.baseText
6363
app/ui/SearchPage.qml+3−5
@@ -204,7 +204,7 @@ Page {
204204 id: emptyStateComponent
205205 EmptyState {
206206 icon.source: searchPage.header !== searchHeader ? Qt.resolvedUrl("../graphics/owlSearch.svg") : Qt.resolvedUrl("../graphics/notFound.svg")
207− title: searchPage.header !== searchHeader ? i18n.tr("Looking to add a new Podcast?") : i18n.tr("No Podcasts found")
207+ title: searchPage.header !== searchHeader ? i18n.tr("Looking to add a new podcast?") : i18n.tr("No Podcasts Found")
208208 subTitle: searchPage.header !== searchHeader ? i18n.tr("Click the 'magnifier' at the top to search or the 'plus' button to add by URL")
209209 : i18n.tr("No podcasts found matching the search term.")
210210 }
@@ -276,8 +276,7 @@ Page {
276276 onClicked: {
277277 if (!model.subscribed) {
278278 Podcasts.subscribe(model.artist, model.name, model.feed, model.image);
279− imageDownloader.feed = model.feed;
280− imageDownloader.download(model.image);
279+ imageDownloader.addDownload(model.feed, model.image)
281280 } else {
282281 var db = Podcasts.init();
283282 db.transaction(function (tx) {
@@ -440,8 +439,7 @@ Page {
440439
441440 if(name != "") {
442441 Podcasts.subscribe(artist, name, feed, image);
443− imageDownloader.feed = feed;
444− imageDownloader.download(image);
442+ imageDownloader.addDownload(feed, image)
445443 tabs.selectedTabIndex = 2;
446444 } else {
447445 PopupUtils.open(subscribeFailedDialog);
app/ui/SettingsPage.qml+214
@@ -19,7 +19,9 @@
1919import QtQuick 2.4
2020import Ubuntu.Components 1.3
2121import Ubuntu.Components.Popups 1.3
22+import QtQuick.LocalStorage 2.0
2223import "../components"
24+import "../podcasts.js" as Podcasts
2325
2426Page {
2527 id: settingsPage
@@ -210,6 +212,103 @@ Page {
210212 onClicked: mainStack.push(Qt.resolvedUrl("../settings/DownloadSetting.qml"))
211213 }
212214
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")
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+
213312 HeaderListItem {
214313 // TRANSLATORS: Shortened form of "Miscellaneous" which is shown to denote other setting options
215314 // that doesn't fit into any other category.
@@ -238,6 +337,121 @@ Page {
238337 }
239338 }
240339 }
340+
341+ function getPodcastCoverArt(name, feedUrl, image) {
342+ // Default first to iTunes to fetch cover art. Fallback to feed url if podcast cannot be
343+ // found in iTunes.
344+ getCoverArtItunes(name, feedUrl, image)
345+ }
346+
347+ function getCoverArtItunes(name, feedUrl, image) {
348+ var coverArt = ""
349+ var url = "https://itunes.apple.com/search?term=" + name + "&media=podcast&entity=podcast"
350+ var xhr = new XMLHttpRequest;
351+ xhr.open("GET", url);
352+ xhr.onreadystatechange = function() {
353+ if (xhr.readyState === XMLHttpRequest.DONE) {
354+ var json = JSON.parse(xhr.responseText);
355+ var db = Podcasts.init();
356+
357+ for(var i in json.results) {
358+ if (name == json.results[i].trackName) {
359+ if (feedUrl == json.results[i].feedUrl) {
360+ fileManager.deleteFile(image);
361+ coverArt = json.results[i].artworkUrl600
362+ if (coverArt) {
363+ imageDownloader.addDownload(feedUrl, coverArt)
364+ }
365+ }
366+ }
367+ }
368+
369+ // If the podcast is not found on iTunes, fallback to its feed url for fetching cover art
370+ if (coverArt == "") {
371+ getCoverArtFeedUrl(name, feedUrl, image)
372+ }
373+ }
374+ }
375+ xhr.send();
376+ }
377+
378+ function getCoverArtFeedUrl(name, feedUrl, image) {
379+ console.log("Fetching cover art from feed")
380+ var coverArt
381+ var xhr = new XMLHttpRequest;
382+ xhr.open("GET", feedUrl)
383+ xhr.onreadystatechange = function () {
384+ if (xhr.readyState === XMLHttpRequest.DONE) {
385+ var e = xhr.responseXML.documentElement;
386+
387+ for(var h = 0; h < e.childNodes.length; h++) {
388+ if(e.childNodes[h].nodeName === "channel") {
389+ var c = e.childNodes[h];
390+ for(var j = 0; j < c.childNodes.length; j++) {
391+ var nodeName = c.childNodes[j].nodeName;
392+ if (nodeName === "image") {
393+ var el = c.childNodes[j];
394+ for (var l = 0; l < el.attributes.length; l++) {
395+ if(el.attributes[l].nodeName === "href") {
396+ coverArt = el.attributes[l].nodeValue;
397+ if (coverArt) {
398+ fileManager.deleteFile(image);
399+ imageDownloader.addDownload(feedUrl, coverArt)
400+ }
401+ }
402+ }
403+ }
404+ }
405+ }
406+ }
407+ }
408+ }
409+
410+ xhr.send()
411+ }
412+
413+ function removeOrphans() {
414+ var filelist = fileManager.getDownloadedEpisodes()
415+
416+ orphanListItem.fileCount = 0
417+ orphanListItem.linkCount = 0
418+
419+ var db = Podcasts.init()
420+ db.transaction(function (tx) {
421+ var rs = tx.executeSql("SELECT rowid, * FROM Podcast ORDER BY name ASC");
422+
423+ // Go through every downloaded file and check if they are orphaned
424+ for (var l = 0; l < filelist.length; l++) {
425+ var rs2 = tx.executeSql("SELECT rowid FROM Episode WHERE downloadedfile = ?", [fileManager.podcastDirectory + "/" + filelist[l]])
426+
427+ if (rs2.rows.length == 0) {
428+ orphanListItem.fileCount++
429+ fileManager.deleteFile(fileManager.podcastDirectory + "/" + filelist[l])
430+ console.log("[LOG]: Removed Orphan File: " + fileManager.podcastDirectory + "/" + filelist[l])
431+ }
432+ }
433+
434+ // Filter all episode with downloadedfile links and check if they are orphaned links or not
435+ var rs2 = tx.executeSql("SELECT rowid, * FROM Episode WHERE downloadedfile IS NOT NULL")
436+ for (var i=0; i<rs2.rows.length; i++) {
437+ var isOrphanLink = true
438+ var episode = rs2.rows.item(i);
439+
440+ for (var l = 0; l < filelist.length; l++) {
441+ if(episode.downloadedfile === fileManager.podcastDirectory + "/" + filelist[l]) {
442+ isOrphanLink = false
443+ break
444+ }
445+ }
446+
447+ if (isOrphanLink) {
448+ orphanListItem.linkCount++
449+ tx.executeSql("UPDATE Episode SET downloadedfile = NULL WHERE guid = ?", [episode.guid]);
450+ console.log("[LOG]: Removed Orphan Link: " + episode.downloadedfile)
451+ }
452+ }
453+ });
454+ }
241455}
242456
243457
backend/modules/Podbird/filemanager.cpp+18−5
@@ -24,7 +24,8 @@
2424#include "filemanager.h"
2525
2626FileManager::FileManager(QObject *parent):
27− QObject(parent)
27+ QObject(parent),
28+ m_podcastDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QDir::separator() + "podcasts")
2829{
2930 // Remove any downloads that haven't been collected previously (because we were closed before they finished)
3031 QString downloadManagerPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + "ubuntu-download-manager" + QDir::separator() + "com.mikeasoft.podbird";
@@ -38,6 +39,11 @@ FileManager::~FileManager() {
3839
3940}
4041
42+QString FileManager::podcastDirectory() const
43+{
44+ return m_podcastDir;
45+}
46+
4147void FileManager::deleteFile(QString path) {
4248 QFile file(path);
4349 if (file.exists()) {
@@ -46,17 +52,16 @@ void FileManager::deleteFile(QString path) {
4652}
4753
4854QString FileManager::saveDownload(QString origPath) {
49− QString destination = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QDir::separator() + "podcasts";
50− QDir destDir(destination);
55+ QDir destDir(m_podcastDir);
5156 if(!destDir.exists()) {
52− destDir.mkpath(destination);
57+ destDir.mkpath(m_podcastDir);
5358 }
5459 QFileInfo fi(origPath);
5560 QFile *destFile;
5661 QString filePath;
5762 int attempts = 0;
5863 do {
59− filePath = destination + QDir::separator() + fi.fileName();
64+ filePath = m_podcastDir + QDir::separator() + fi.fileName();
6065 if (attempts > 0) {
6166 filePath += "." + QString::number(attempts);
6267 }
@@ -66,3 +71,11 @@ QString FileManager::saveDownload(QString origPath) {
6671 QFile::rename(origPath, filePath);
6772 return filePath;
6873}
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
2525{
2626 Q_OBJECT
2727
28+ // READONLY Property to return the podcast directory path
29+ Q_PROPERTY( QString podcastDirectory
30+ READ podcastDirectory
31+ CONSTANT)
32+
2833public:
2934 explicit FileManager(QObject *parent = 0);
3035 ~FileManager();
3136
37+ QString podcastDirectory() const;
38+
3239public:
3340 Q_INVOKABLE void deleteFile(QString path);
3441 Q_INVOKABLE QString saveDownload(QString path);
42+ Q_INVOKABLE QStringList getDownloadedEpisodes();
43+
44+private:
45+ QString m_podcastDir;
3546};
3647
3748#endif // FILEMANAGER_H
po/com.mikeasoft.podbird.pot+76−40
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: \n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2016-03-19 19:22+0530\n"
11+"POT-Creation-Date: 2016-12-08 17:29+0000\n"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,19 +18,19 @@ msgstr ""
1818"Content-Transfer-Encoding: 8bit\n"
1919"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
2020
21−#: ../app/components/TabsList.qml:27 ../app/ui/EpisodesTab.qml:48
21+#: ../app/components/TabsList.qml:28 ../app/ui/EpisodesTab.qml:48
2222msgid "Episodes"
2323msgstr ""
2424
25−#: ../app/components/TabsList.qml:35 ../app/ui/SearchPage.qml:41
25+#: ../app/components/TabsList.qml:37 ../app/ui/SearchPage.qml:41
2626msgid "Add New Podcasts"
2727msgstr ""
2828
29−#: ../app/components/TabsList.qml:43 ../app/ui/PodcastsTab.qml:42
29+#: ../app/components/TabsList.qml:46 ../app/ui/PodcastsTab.qml:42
3030msgid "Podcasts"
3131msgstr ""
3232
33−#: ../app/components/TabsList.qml:51 ../app/ui/SettingsPage.qml:32
33+#: ../app/components/TabsList.qml:55 ../app/ui/SettingsPage.qml:34
3434msgid "Settings"
3535msgstr ""
3636
@@ -38,24 +38,24 @@ msgstr ""
3838msgid "Skip"
3939msgstr ""
4040
41−#: ../app/podcasts.js:282
41+#: ../app/podcasts.js:298
4242#, no-c-format, qt-format
4343msgid "%1 hr %2 min"
4444msgstr ""
4545
46−#: ../app/podcasts.js:291
46+#: ../app/podcasts.js:307
4747#, no-c-format, qt-format
4848msgid "%1 hr"
4949msgstr ""
5050
51−#: ../app/podcasts.js:299
51+#: ../app/podcasts.js:315
5252#, no-c-format, qt-format
5353msgid "%1 min"
5454msgstr ""
5555
5656#. TRANSLATORS: About as in information about the app
5757#: ../app/settings/About.qml:28 ../app/settings/About.qml:46
58−#: ../app/ui/SettingsPage.qml:222
58+#: ../app/ui/SettingsPage.qml:321
5959msgid "About"
6060msgstr ""
6161
@@ -140,17 +140,17 @@ msgid_plural "%1 episodes"
140140msgstr[0] ""
141141msgstr[1] ""
142142
143−#: ../app/settings/ThemeSetting.qml:29 ../app/ui/SettingsPage.qml:144
143+#: ../app/settings/ThemeSetting.qml:29 ../app/ui/SettingsPage.qml:146
144144msgid "Theme"
145145msgstr ""
146146
147147#. TRANSLATORS: Light Theme
148−#: ../app/settings/ThemeSetting.qml:41 ../app/ui/SettingsPage.qml:145
148+#: ../app/settings/ThemeSetting.qml:41 ../app/ui/SettingsPage.qml:147
149149msgid "Light"
150150msgstr ""
151151
152152#. TRANSLATORS: Dark Theme
153−#: ../app/settings/ThemeSetting.qml:43 ../app/ui/SettingsPage.qml:145
153+#: ../app/settings/ThemeSetting.qml:43 ../app/ui/SettingsPage.qml:147
154154msgid "Dark"
155155msgstr ""
156156
@@ -214,7 +214,7 @@ msgid "Are you sure you want to unsubscribe from <b>%1</b>?"
214214msgstr ""
215215
216216#: ../app/ui/EpisodesPage.qml:323 ../app/ui/SearchPage.qml:130
217−#: ../app/ui/SettingsPage.qml:83 ../app/ui/SettingsPage.qml:118
217+#: ../app/ui/SettingsPage.qml:85 ../app/ui/SettingsPage.qml:120
218218msgid "Cancel"
219219msgstr ""
220220
@@ -223,7 +223,7 @@ msgid "Episode Description"
223223msgstr ""
224224
225225#: ../app/ui/EpisodesPage.qml:356 ../app/ui/EpisodesTab.qml:418
226−#: ../app/ui/SearchPage.qml:179
226+#: ../app/ui/SearchPage.qml:179 ../app/ui/SettingsPage.qml:303
227227msgid "Close"
228228msgstr ""
229229
@@ -352,13 +352,13 @@ msgstr ""
352352msgid "No Podcast Subscriptions"
353353msgstr ""
354354
355−#: ../app/ui/PodcastsTab.qml:127
355+#: ../app/ui/PodcastsTab.qml:127 ../app/ui/SearchPage.qml:207
356356msgid "No Podcasts Found"
357357msgstr ""
358358
359359#: ../app/ui/PodcastsTab.qml:128
360360msgid ""
361−"You haven't subscribed to any podcasts yet, visit the 'Find New Podcasts' "
361+"You haven't subscribed to any podcasts yet, visit the 'Add New Podcasts' "
362362"page to add some."
363363msgstr ""
364364
@@ -394,11 +394,7 @@ msgid "Please check the URL and try again"
394394msgstr ""
395395
396396#: ../app/ui/SearchPage.qml:207
397−msgid "Looking to add a new Podcast?"
398−msgstr ""
399−
400−#: ../app/ui/SearchPage.qml:207
401−msgid "No Podcasts found"
397+msgid "Looking to add a new podcast?"
402398msgstr ""
403399
404400#: ../app/ui/SearchPage.qml:208
@@ -412,81 +408,121 @@ msgstr ""
412408
413409#. TRANSLATORS: The first argument here is the date of when the podcast was last updated followed by
414410#. the podcast description.
415−#: ../app/ui/SearchPage.qml:314
411+#: ../app/ui/SearchPage.qml:313
416412#, qt-format
417413msgid ""
418414"Last Updated: %1\n"
419415"%2"
420416msgstr ""
421417
422−#: ../app/ui/SearchPage.qml:371
418+#: ../app/ui/SearchPage.qml:370
423419msgid "Not Available"
424420msgstr ""
425421
426422#. TRANSLATORS: This strings refers to the seeking of the episode playback. Users can set how far they
427423#. want to seek forward when pressing on this button.
428−#: ../app/ui/SettingsPage.qml:63 ../app/ui/SettingsPage.qml:169
424+#: ../app/ui/SettingsPage.qml:65 ../app/ui/SettingsPage.qml:171
429425msgid "Skip forward"
430426msgstr ""
431427
432−#: ../app/ui/SettingsPage.qml:70 ../app/ui/SettingsPage.qml:105
433−#: ../app/ui/SettingsPage.qml:170 ../app/ui/SettingsPage.qml:177
428+#: ../app/ui/SettingsPage.qml:72 ../app/ui/SettingsPage.qml:107
429+#: ../app/ui/SettingsPage.qml:172 ../app/ui/SettingsPage.qml:179
434430#, qt-format
435431msgid "%1 seconds"
436432msgstr ""
437433
438−#: ../app/ui/SettingsPage.qml:75 ../app/ui/SettingsPage.qml:110
434+#: ../app/ui/SettingsPage.qml:77 ../app/ui/SettingsPage.qml:112
439435msgid "Ok"
440436msgstr ""
441437
442438#. TRANSLATORS: This strings refers to the seeking of the episode playback. Users can set how far they
443439#. want to seek backward when pressing on this button.
444−#: ../app/ui/SettingsPage.qml:98 ../app/ui/SettingsPage.qml:176
440+#: ../app/ui/SettingsPage.qml:100 ../app/ui/SettingsPage.qml:178
445441msgid "Skip back"
446442msgstr ""
447443
448444#. TRANSLATORS: Shortened form of "Miscellaneous" which is shown to denote other setting options
449445#. that doesn't fit into any other category.
450−#: ../app/ui/SettingsPage.qml:139
446+#: ../app/ui/SettingsPage.qml:141
451447msgid "General Settings"
452448msgstr ""
453449
454−#: ../app/ui/SettingsPage.qml:152
450+#: ../app/ui/SettingsPage.qml:154
455451msgid "Displays podcasts in a list view"
456452msgstr ""
457453
458−#: ../app/ui/SettingsPage.qml:164
454+#: ../app/ui/SettingsPage.qml:166
459455msgid "Playback Settings"
460456msgstr ""
461457
462−#: ../app/ui/SettingsPage.qml:182
458+#: ../app/ui/SettingsPage.qml:184
463459msgid "Podcast Episode Settings"
464460msgstr ""
465461
466−#: ../app/ui/SettingsPage.qml:188
462+#: ../app/ui/SettingsPage.qml:190
467463msgid "Automatically delete old episodes"
468464msgstr ""
469465
470−#: ../app/ui/SettingsPage.qml:190
466+#: ../app/ui/SettingsPage.qml:192
471467msgid ""
472468"Delete episodes that are older than a given number of days for each podcast"
473469msgstr ""
474470
475−#: ../app/ui/SettingsPage.qml:202
471+#: ../app/ui/SettingsPage.qml:204
476472msgid "Automatically download new episodes"
477473msgstr ""
478474
479−#: ../app/ui/SettingsPage.qml:204
475+#: ../app/ui/SettingsPage.qml:206
480476msgid "Default number of new episodes to download for each podcast"
481477msgstr ""
482478
479+#: ../app/ui/SettingsPage.qml:223
480+msgid "Refresh podcast artwork"
481+msgstr ""
482+
483+#: ../app/ui/SettingsPage.qml:225
484+msgid "Update all podcasts artwork and fix missing ones"
485+msgstr ""
486+
487+#: ../app/ui/SettingsPage.qml:254
488+msgid "Storage Settings"
489+msgstr ""
490+
491+#: ../app/ui/SettingsPage.qml:265
492+msgid "Delete orphaned files and links"
493+msgstr ""
494+
495+#: ../app/ui/SettingsPage.qml:267
496+msgid "Free space by removing orphaned downloaded files and links"
497+msgstr ""
498+
499+#: ../app/ui/SettingsPage.qml:297
500+msgid "Removed orphaned files and links"
501+msgstr ""
502+
503+#: ../app/ui/SettingsPage.qml:297
504+msgid "No orphans found!"
505+msgstr ""
506+
507+#: ../app/ui/SettingsPage.qml:298
508+msgid ""
509+"All orphaned files have been deleted to recover disk space. Orphaned links "
510+"pointing at invalid files have also been cleaned up."
511+msgstr ""
512+
513+#: ../app/ui/SettingsPage.qml:300
514+msgid ""
515+"No orphaned files have been found to recover disk space. Podbird database is "
516+"clean."
517+msgstr ""
518+
483519#. TRANSLATORS: Shortened form of "Miscellaneous" which is shown to denote other setting options
484520#. that doesn't fit into any other category.
485−#: ../app/ui/SettingsPage.qml:216
521+#: ../app/ui/SettingsPage.qml:315
486522msgid "Misc."
487523msgstr ""
488524
489−#: ../app/ui/SettingsPage.qml:232
525+#: ../app/ui/SettingsPage.qml:331
490526msgid "Report Bug"
491527msgstr ""
492528
@@ -574,10 +610,10 @@ msgstr ""
574610msgid "Finish"
575611msgstr ""
576612
577−#: /home/krnekhelesh/Development/devel-branch-sync-3-build/po/Podbird.desktop.in.h:1
613+#: /home/krnekhelesh/Development/Builds/build-devel-branch-sync-4-UbuntuSDK_for_desktop_GCC_amd64_ubuntu_sdk_15_04-Default/po/Podbird.desktop.in.h:1
578614msgid "The chirpiest podcast manager for Ubuntu"
579615msgstr ""
580616
581−#: /home/krnekhelesh/Development/devel-branch-sync-3-build/po/Podbird.desktop.in.h:2
617+#: /home/krnekhelesh/Development/Builds/build-devel-branch-sync-4-UbuntuSDK_for_desktop_GCC_amd64_ubuntu_sdk_15_04-Default/po/Podbird.desktop.in.h:2
582618msgid "podcast;audio;itunes;broadcast;digital;stream;podcatcher;video;vodcast;"
583619msgstr ""