Sort episodes by published date when sending to folder
[gpodder.git] / src / gpodder / deviceplaylist.py
blob636037201bef8e510a5b461d8f1ed777a34b58c4
1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2011 Thomas Perl and the gPodder Team
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 import logging
21 import os
23 import gpodder
24 from gpodder import util
25 from gpodder.sync import (episode_filename_on_device,
26 episode_foldername_on_device)
28 import gi # isort:skip
29 gi.require_version('Gio', '2.0') # isort:skip
30 from gi.repository import Gio, GLib # isort:skip
32 _ = gpodder.gettext
35 logger = logging.getLogger(__name__)
38 class gPodderDevicePlaylist(object):
39 def __init__(self, config, playlist_name):
40 self._config = config
41 self.linebreak = '\r\n'
42 self.playlist_file = (
43 util.sanitize_filename(playlist_name, self._config.device_sync.max_filename_length)
44 + '.' + self._config.device_sync.playlists.extension)
45 device_folder = util.new_gio_file(self._config.device_sync.device_folder)
46 self.playlist_folder = device_folder.resolve_relative_path(self._config.device_sync.playlists.folder)
48 self.mountpoint = None
49 try:
50 self.mountpoint = self.playlist_folder.find_enclosing_mount().get_root()
51 except GLib.Error as err:
52 logger.error('find_enclosing_mount folder %s failed: %s', self.playlist_folder.get_uri(), err.message)
54 if not self.mountpoint:
55 self.mountpoint = self.playlist_folder
56 logger.warning('could not find mount point for MP3 player - using %s as MP3 player root', self.mountpoint.get_uri())
57 self.playlist_absolute_filename = self.playlist_folder.resolve_relative_path(self.playlist_file)
59 def build_extinf(self, filename):
60 # TODO: Windows playlists
61 # if self._config.mp3_player_playlist_win_path:
62 # filename = filename.replace('\\', os.sep)
64 # # rebuild the whole filename including the mountpoint
65 # if self._config.device_sync.playlist_absolute_path:
66 # absfile = os.path.join(self.mountpoint,filename)
67 # else: #TODO: Test rel filenames
68 # absfile = util.rel2abs(filename, os.path.dirname(self.playlist_file))
70 # fallback: use the basename of the file
71 (title, extension) = os.path.splitext(os.path.basename(filename))
73 return "#EXTINF:0,%s%s" % (title.strip(), self.linebreak)
75 def read_m3u(self):
76 """
77 read all files from the existing playlist
78 """
79 tracks = []
80 logger.info("Read data from the playlistfile %s" % self.playlist_absolute_filename.get_uri())
81 if self.playlist_absolute_filename.query_exists():
82 stream = Gio.DataInputStream.new(self.playlist_absolute_filename.read())
83 while True:
84 line = stream.read_line_utf8()[0]
85 if not line:
86 break
87 if not line.startswith('#EXT'):
88 tracks.append(line.rstrip('\r\n'))
89 stream.close()
90 return tracks
92 def get_filename_for_playlist(self, episode):
93 """
94 get the filename for the given episode for the playlist
95 """
96 return episode_filename_on_device(self._config, episode)
98 def get_absolute_filename_for_playlist(self, episode):
99 """
100 get the filename including full path for the given episode for the playlist
102 filename = self.get_filename_for_playlist(episode)
103 foldername = episode_foldername_on_device(self._config, episode)
104 if foldername:
105 filename = os.path.join(foldername, filename)
106 if self._config.device_sync.playlists.use_absolute_path:
107 filename = os.path.join(util.relpath(self._config.device_sync.device_folder, self.mountpoint.get_uri()), filename)
108 return filename
110 def write_m3u(self, episodes):
112 write the list into the playlist on the device
114 logger.info('Writing playlist file: %s', self.playlist_file)
115 if not util.make_directory(self.playlist_folder):
116 raise IOError(_('Folder %s could not be created.') % self.playlist_folder, _('Error writing playlist'))
117 else:
118 # work around libmtp devices potentially having limited capabilities for partial writes
119 is_mtp = self.playlist_folder.get_uri().startswith("mtp://")
120 tempfile = None
121 if is_mtp:
122 tempfile = Gio.File.new_tmp()
123 fs = tempfile[1].get_output_stream()
124 else:
125 fs = self.playlist_absolute_filename.replace(None, False, Gio.FileCreateFlags.NONE)
127 os = Gio.DataOutputStream.new(fs)
128 os.put_string('#EXTM3U%s' % self.linebreak)
129 for current_episode in episodes:
130 filename = self.get_filename_for_playlist(current_episode)
131 os.put_string(self.build_extinf(filename))
132 filename = self.get_absolute_filename_for_playlist(current_episode)
133 os.put_string(filename)
134 os.put_string(self.linebreak)
135 os.close()
137 if is_mtp:
138 try:
139 tempfile[0].copy(self.playlist_absolute_filename, Gio.FileCopyFlags.OVERWRITE)
140 except GLib.Error as err:
141 logger.error('copying playlist to mtp device file %s failed: %s',
142 self.playlist_absolute_filename.get_uri(), err.message)
143 tempfile[0].delete()