diff --git a/src/renderer/components/data-settings/data-settings.js b/src/renderer/components/data-settings/data-settings.js
index 7963873a..2321deb8 100644
--- a/src/renderer/components/data-settings/data-settings.js
+++ b/src/renderer/components/data-settings/data-settings.js
@@ -54,6 +54,9 @@ export default Vue.extend({
profileList: function () {
return this.$store.getters.getProfileList
},
+ allPlaylists: function () {
+ return this.$store.getters.getAllPlaylists
+ },
importSubscriptionsPromptNames: function () {
const importFreeTube = this.$t('Settings.Data Settings.Import FreeTube')
const importYouTube = this.$t('Settings.Data Settings.Import YouTube')
@@ -245,7 +248,6 @@ export default Vue.extend({
return
}
const textDecode = new TextDecoder('utf-8').decode(data)
- console.log(textDecode)
const youtubeSubscriptions = textDecode.split('\n')
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
const subscriptions = []
@@ -317,8 +319,6 @@ export default Vue.extend({
let textDecode = new TextDecoder('utf-8').decode(data)
textDecode = JSON.parse(textDecode)
- console.log(textDecode)
-
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
const subscriptions = []
@@ -1075,6 +1075,168 @@ export default Vue.extend({
})
},
+ importPlaylists: async function () {
+ const options = {
+ properties: ['openFile'],
+ filters: [
+ {
+ name: 'Database File',
+ extensions: ['db']
+ }
+ ]
+ }
+
+ const response = await this.showOpenDialog(options)
+ if (response.canceled || response.filePaths.length === 0) {
+ return
+ }
+
+ const filePath = response.filePaths[0]
+
+ fs.readFile(filePath, async (err, data) => {
+ if (err) {
+ const message = this.$t('Settings.Data Settings.Unable to read file')
+ this.showToast({
+ message: `${message}: ${err}`
+ })
+ return
+ }
+
+ const playlists = JSON.parse(data)
+
+ playlists.forEach(async (playlistData) => {
+ // We would technically already be done by the time the data is parsed,
+ // however we want to limit the possibility of malicious data being sent
+ // to the app, so we'll only grab the data we need here.
+ const requiredKeys = [
+ 'playlistName',
+ 'videos'
+ ]
+
+ const optionalKeys = [
+ '_id',
+ 'protected',
+ 'removeOnWatched'
+ ]
+
+ const requiredVideoKeys = [
+ 'videoId',
+ 'title',
+ 'author',
+ 'authorId',
+ 'published',
+ 'lengthSeconds',
+ 'timeAdded',
+ 'isLive',
+ 'paid',
+ 'type'
+ ]
+
+ const playlistObject = {}
+
+ Object.keys(playlistData).forEach((key) => {
+ if (!requiredKeys.includes(key) && !optionalKeys.includes(key)) {
+ const message = `${this.$t('Settings.Data Settings.Unknown data key')}: ${key}`
+ this.showToast({
+ message: message
+ })
+ } else if (key === 'videos') {
+ const videoArray = []
+ playlistData.videos.forEach((video) => {
+ let hasAllKeys = true
+ Object.keys(video).forEach((videoKey) => {
+ if (!requiredVideoKeys.includes(videoKey)) {
+ hasAllKeys = false
+ }
+ })
+
+ if (hasAllKeys) {
+ videoArray.push(video)
+ }
+ })
+
+ playlistObject[key] = videoArray
+ } else {
+ playlistObject[key] = playlistData[key]
+ }
+ })
+
+ const objectKeys = Object.keys(playlistObject)
+
+ if ((objectKeys.length < requiredKeys.length) || playlistObject.videos.length === 0) {
+ const message = this.$t('Settings.Data Settings.Playlist insufficient data').replace('$', playlistData.playlistName)
+ this.showToast({
+ message: message
+ })
+ } else {
+ const existingPlaylist = this.allPlaylists.find((playlist) => {
+ return playlist.playlistName === playlistObject.playlistName
+ })
+
+ if (existingPlaylist !== undefined) {
+ playlistObject.videos.forEach((video) => {
+ const existingVideo = existingPlaylist.videos.find((x) => {
+ return x.videoId === video.videoId
+ })
+
+ if (existingVideo === undefined) {
+ const payload = {
+ playlistName: existingPlaylist.playlistName,
+ videoData: video
+ }
+
+ this.addVideo(payload)
+ }
+ })
+ } else {
+ this.addPlaylist(playlistObject)
+ }
+ }
+ })
+
+ this.showToast({
+ message: this.$t('Settings.Data Settings.All playlists has been successfully imported')
+ })
+ })
+ },
+
+ exportPlaylists: async function () {
+ const date = new Date().toISOString().split('T')[0]
+ const exportFileName = 'freetube-playlists-' + date + '.db'
+
+ const options = {
+ defaultPath: exportFileName,
+ filters: [
+ {
+ name: 'Database File',
+ extensions: ['db']
+ }
+ ]
+ }
+
+ const response = await this.showSaveDialog(options)
+ if (response.canceled || response.filePath === '') {
+ // User canceled the save dialog
+ return
+ }
+
+ const filePath = response.filePath
+
+ fs.writeFile(filePath, JSON.stringify(this.allPlaylists), (writeErr) => {
+ if (writeErr) {
+ const message = this.$t('Settings.Data Settings.Unable to write file')
+ this.showToast({
+ message: `${message}: ${writeErr}`
+ })
+ return
+ }
+
+ this.showToast({
+ message: this.$t('Settings.Data Settings.All playlists has been successfully exported')
+ })
+ })
+ },
+
async convertOldFreeTubeFormatToNew(oldData) {
const convertedData = []
for (const channel of oldData) {
@@ -1178,7 +1340,9 @@ export default Vue.extend({
'calculateColorLuminance',
'showOpenDialog',
'showSaveDialog',
- 'getUserDataPath'
+ 'getUserDataPath',
+ 'addPlaylist',
+ 'addVideo'
]),
...mapMutations([
diff --git a/src/renderer/components/data-settings/data-settings.vue b/src/renderer/components/data-settings/data-settings.vue
index e737e2fe..c4b33595 100644
--- a/src/renderer/components/data-settings/data-settings.vue
+++ b/src/renderer/components/data-settings/data-settings.vue
@@ -44,6 +44,16 @@
@click="openProfileSettings"
/>
+