Esempio n. 1
0
    def __init__( self):
        log('Creating gPodderLib()', sender=self)
        if gpodder.interface == gpodder.MAEMO:
            gpodder_dir='/media/mmc2/gpodder/'
            self.osso_c=osso.Context('gpodder_osso_sender', '1.0', False)
        else:
            gpodder_dir=os.path.expanduser('~/.config/gpodder/')
        util.make_directory( gpodder_dir)

        self.tempdir=gpodder_dir
        self.feed_cache_file=os.path.join(gpodder_dir, 'feedcache.pickle.db')
        self.channel_settings_file=os.path.join(gpodder_dir, 'channelsettings.pickle.db')
        self.episode_metainfo_file=os.path.join(gpodder_dir, 'episodemetainfo.pickle.db')

        self.channel_opml_file=os.path.join(gpodder_dir, 'channels.opml')
        self.channel_xml_file=os.path.join(gpodder_dir, 'channels.xml')

        if os.path.exists(self.channel_xml_file) and not os.path.exists(self.channel_opml_file):
            log('Trying to migrate channel list (channels.xml => channels.opml)', sender=self)
            self.migrate_channels_xml()

        self.config=config.Config( os.path.join( gpodder_dir, 'gpodder.conf'))
        util.make_directory(self.config.bittorrent_dir)

        # We need to make a seamless upgrade, so by default the video player is not specified
        # so the first time this application is run it will detect this and set it to the same 
        # as the audio player.  This keeps gPodder functionality identical to that prior to the 
        # upgrade.   The user can then set a specific video player if they so wish.	
        if self.config.videoplayer == 'unspecified':
            self.config.videoplayer=self.config.player	

        self.__download_history=HistoryStore( os.path.join( gpodder_dir, 'download-history.txt'))
        self.__playback_history=HistoryStore( os.path.join( gpodder_dir, 'playback-history.txt'))
        self.__locked_history=HistoryStore( os.path.join( gpodder_dir, 'lock-history.txt'))
Esempio n. 2
0
    def __init__(self):
        """Create a new gPodder API instance

        Connects to the database and loads the configuration.
        """
        util.make_directory(gpodder.home)
        gpodder.load_plugins()

        self._db = dbsqlite.Database(gpodder.database_file)
        self._config = config.Config(gpodder.config_file)
Esempio n. 3
0
    def __init__(self):
        """Create a new gPodder API instance

        Connects to the database and loads the configuration.
        """
        util.make_directory(gpodder.home)
        gpodder.load_plugins()

        self._db = dbsqlite.Database(gpodder.database_file)
        self._config = config.Config(gpodder.config_file)
Esempio n. 4
0
    def __init__(self,
                 config_class=config.Config,
                 database_class=storage.Database,
                 model_class=model.Model,
                 prefix=None,
                 verbose=True,
                 progname='gpodder'):
        self._set_socket_timeout()

        self.prefix = prefix
        if not self.prefix:
            # XXX
            self.prefix = os.path.abspath('.')

        home = os.path.expanduser('~')

        xdg_data_home = os.environ.get('XDG_DATA_HOME',
                os.path.join(home, '.local', 'share'))
        xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
                os.path.join(home, '.config'))
        xdg_cache_home = os.environ.get('XDG_CACHE_HOME',
                os.path.join(home, '.cache'))

        self.data_home = os.path.join(xdg_data_home, progname)
        self.config_home = os.path.join(xdg_config_home, progname)
        self.cache_home = os.path.join(xdg_cache_home, progname)

        # Use $GPODDER_HOME to set a fixed config and data folder
        if 'GPODDER_HOME' in os.environ:
            home = os.environ['GPODDER_HOME']
            self.data_home = self.config_home = self.cache_home = home

        # Setup logging
        log.setup(self.cache_home, verbose)
        self.logger = logging.getLogger(__name__)

        config_file = os.path.join(self.config_home, 'Settings.json')
        database_file = os.path.join(self.data_home, 'Database')
        # Downloads go to <data_home> or $GPODDER_DOWNLOAD_DIR
        self.downloads = os.environ.get('GPODDER_DOWNLOAD_DIR',
                os.path.join(self.data_home))

        # Initialize the gPodder home directories
        util.make_directory(self.data_home)
        util.make_directory(self.config_home)

        # Open the database and configuration file
        self.db = database_class(database_file)
        self.model = model_class(self)
        self.config = config_class(config_file)

        # Load installed/configured plugins
        self._load_plugins()

        self.cover_downloader = coverart.CoverDownloader(self)
Esempio n. 5
0
    def __init__(self,
                 config_class=config.Config,
                 database_class=storage.Database,
                 model_class=model.Model,
                 verbose=True,
                 progname='gpodder',
                 stdout=False):
        self._set_socket_timeout()

        home = os.path.expanduser('~')

        xdg_data_home = os.environ.get('XDG_DATA_HOME',
                                       os.path.join(home, '.local', 'share'))
        xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
                                         os.path.join(home, '.config'))
        xdg_cache_home = os.environ.get('XDG_CACHE_HOME',
                                        os.path.join(home, '.cache'))

        self.data_home = os.path.join(xdg_data_home, progname)
        self.config_home = os.path.join(xdg_config_home, progname)
        self.cache_home = os.path.join(xdg_cache_home, progname)

        # Use $GPODDER_HOME to set a fixed config and data folder
        if 'GPODDER_HOME' in os.environ:
            home = os.environ['GPODDER_HOME']
            self.data_home = self.config_home = self.cache_home = home

        # Setup logging
        log.setup(self.cache_home, verbose, stdout)
        self.logger = logging.getLogger(__name__)

        config_file = os.path.join(self.config_home, 'Settings.json')
        database_file = os.path.join(self.data_home, 'Database')
        # Downloads go to <data_home> or $GPODDER_DOWNLOAD_DIR
        self.downloads = os.environ.get('GPODDER_DOWNLOAD_DIR',
                                        os.path.join(self.data_home))

        # Initialize the gPodder home directories
        util.make_directory(self.data_home)
        util.make_directory(self.config_home)

        # Open the database and configuration file
        self.db = database_class(database_file, verbose)
        self.model = model_class(self)
        self.config = config_class(config_file)

        # Load installed/configured plugins
        self._load_plugins()

        self.cover_downloader = coverart.CoverDownloader(self)
Esempio n. 6
0
    def add_track(self, task, reporthook=None):
        episode = task.episode
        self.notify('status', _('Adding %s') % episode.title)

        # get the folder on the device
        folder = self.get_episode_folder_on_device(episode)

        filename = episode.local_filename(create=False)
        # The file has to exist, if we ought to transfer it, and therefore,
        # local_filename(create=False) must never return None as filename
        assert filename is not None

        from_file = filename

        # verify free space
        needed = util.calculate_size(from_file)
        free = self.get_free_space()
        if free == -1:
            logger.warn('Cannot determine free disk space on device')
        elif needed > free:
            d = {'path': self.destination, 'free': util.format_filesize(free), 'need': util.format_filesize(needed)}
            message = _('Not enough space in %(path)s: %(free)s available, but need at least %(need)s')
            raise SyncFailedException(message % d)

        # get the filename that will be used on the device
        to_file = self.get_episode_file_on_device(episode)
        to_file = folder.get_child(to_file)

        util.make_directory(folder)

        if not to_file.query_exists():
            logger.info('Copying %s => %s',
                    os.path.basename(from_file),
                    to_file.get_uri())
            from_file = Gio.File.new_for_path(from_file)
            try:
                def hookconvert(current_bytes, total_bytes, user_data):
                    return reporthook(current_bytes, 1, total_bytes)
                from_file.copy(to_file, Gio.FileCopyFlags.OVERWRITE, task.cancellable, hookconvert, None)
            except GLib.Error as err:
                if err.matches(Gio.io_error_quark(), Gio.IOErrorEnum.CANCELLED):
                    raise SyncCancelledException()
                logger.error('Error copying %s to %s: %s', from_file.get_uri(), to_file.get_uri(), err.message)
                d = {'from_file': from_file.get_uri(), 'to_file': to_file.get_uri(), 'message': err.message}
                self.errors.append(_('Error copying %(from_file)s to %(to_file)s: %(message)s') % d)
                return False

        return True
Esempio n. 7
0
 def write_m3u(self):
     """
     write the list into the playlist on the device
     """
     log('Writing playlist file: %s', self.playlist_file, sender=self)
     playlist_folder = os.path.split(self.playlist_file)[0]
     if not util.make_directory(playlist_folder):
         self.show_message(
             _('Folder %s could not be created.') % playlist_folder,
             _('Error writing playlist'))
     else:
         try:
             fp = open(self.playlist_file, 'w')
             fp.write('#EXTM3U%s' % self.linebreak)
             for icon, checked, filename in self.playlist:
                 fp.write(self.build_extinf(filename))
                 if not checked:
                     fp.write('#')
                 fp.write(filename)
                 fp.write(self.linebreak)
             fp.close()
             self.show_message(
                 _('The playlist on your MP3 player has been updated.'),
                 _('Update successful'))
         except IOError, ioe:
             self.show_message(str(ioe), _('Error writing playlist file'))
Esempio n. 8
0
 def write_m3u(self, episodes):
     """
     write the list into the playlist on the device
     """
     logger.info('Writing playlist file: %s', self.playlist_file)
     if not util.make_directory(self.playlist_folder):
         raise IOError(
             _('Folder %s could not be created.') % self.playlist_folder,
             _('Error writing playlist'))
     else:
         fp = open(os.path.join(self.playlist_folder, self.playlist_file),
                   'w')
         fp.write('#EXTM3U%s' % self.linebreak)
         for current_episode in episodes:
             filename_base = util.sanitize_filename(
                 current_episode.sync_filename(
                     self._config.device_sync.custom_sync_name_enabled,
                     self._config.device_sync.custom_sync_name),
                 self._config.device_sync.max_filename_length)
             filename = filename_base + os.path.splitext(
                 current_episode.local_filename(create=False))[1].lower()
             filename = self.get_filename_for_playlist(current_episode)
             fp.write(self.build_extinf(filename))
             filename = self.get_absolute_filename_for_playlist(
                 current_episode)
             fp.write(filename)
             fp.write(self.linebreak)
         fp.close()
Esempio n. 9
0
    def get_save_dir(self, force_new=False):
        if self.download_folder is None or force_new:
            # we must change the folder name, because it has not been set manually
            fn_template = util.sanitize_filename(self.title, self.MAX_FOLDERNAME_LENGTH)

            if not fn_template:
                fn_template = util.sanitize_filename(self.url, self.MAX_FOLDERNAME_LENGTH)

            # Find a unique folder name for this podcast
            download_folder = self.find_unique_folder_name(fn_template)

            # Try renaming the download folder if it has been created previously
            if self.download_folder is not None:
                old_folder = os.path.join(self.model.core.downloads, self.download_folder)
                new_folder = os.path.join(self.model.core.downloads, download_folder)
                try:
                    os.rename(old_folder, new_folder)
                except Exception as ex:
                    logger.info('Cannot rename old download folder: %s', old_folder, exc_info=True)

            logger.info('Updating download_folder of %s to %s', self.url, download_folder)
            self.download_folder = download_folder
            self.save()

        save_dir = os.path.join(self.model.core.downloads, self.download_folder)

        # Create save_dir if it does not yet exist
        if not util.make_directory(save_dir):
            logger.error('Could not create save_dir: %s', save_dir)

        return save_dir
Esempio n. 10
0
    def get_save_dir(self, force_new=False):
        if self.download_folder is None or force_new:
            # we must change the folder name, because it has not been set manually
            fn_template = util.sanitize_filename(self.title, self.MAX_FOLDERNAME_LENGTH)

            if not fn_template:
                fn_template = util.sanitize_filename(self.url, self.MAX_FOLDERNAME_LENGTH)

            # Find a unique folder name for this podcast
            download_folder = self.find_unique_folder_name(fn_template)

            # Try removing the download folder if it has been created previously
            if self.download_folder is not None:
                folder = os.path.join(gpodder.downloads, self.download_folder)
                try:
                    os.rmdir(folder)
                except OSError:
                    logger.info('Old download folder is kept for %s', self.url)

            logger.info('Updating download_folder of %s to %s', self.url,
                    download_folder)
            self.download_folder = download_folder
            self.save()

        save_dir = os.path.join(gpodder.downloads, self.download_folder)

        # Create save_dir if it does not yet exist
        if not util.make_directory(save_dir):
            logger.error('Could not create save_dir: %s', save_dir)

        return save_dir
Esempio n. 11
0
    def write_m3u(self, episodes):
        """
        write the list into the playlist on the device
        """
        logger.info('Writing playlist file: %s', self.playlist_file)
        if not util.make_directory(self.playlist_folder):
            raise IOError(_('Folder %s could not be created.') % self.playlist_folder, _('Error writing playlist'))
        else:
            # work around libmtp devices potentially having limited capabilities for partial writes
            is_mtp = self.playlist_folder.get_uri().startswith("mtp://")
            tempfile = None
            if is_mtp:
                tempfile = Gio.File.new_tmp()
                fs = tempfile[1].get_output_stream()
            else:
                fs = self.playlist_absolute_filename.replace(None, False, Gio.FileCreateFlags.NONE)

            os = Gio.DataOutputStream.new(fs)
            os.put_string('#EXTM3U%s' % self.linebreak)
            for current_episode in episodes:
                filename = self.get_filename_for_playlist(current_episode)
                os.put_string(self.build_extinf(filename))
                filename = self.get_absolute_filename_for_playlist(current_episode)
                os.put_string(filename)
                os.put_string(self.linebreak)
            os.close()

            if is_mtp:
                try:
                    tempfile[0].copy(self.playlist_absolute_filename, Gio.FileCopyFlags.OVERWRITE)
                except GLib.Error as err:
                    logger.error('copying playlist to mtp device file %s failed: %s',
                        self.playlist_absolute_filename.get_uri(), err.message)
                tempfile[0].delete()
Esempio n. 12
0
    def get_save_dir(self, force_new=False):
        if self.download_folder is None or force_new:
            # we must change the folder name, because it has not been set manually
            fn_template = util.sanitize_filename(self.title, self.MAX_FOLDERNAME_LENGTH)

            if not fn_template:
                fn_template = util.sanitize_filename(self.url, self.MAX_FOLDERNAME_LENGTH)

            # Find a unique folder name for this podcast
            download_folder = self.find_unique_folder_name(fn_template)

            # Try removing the download folder if it has been created previously
            if self.download_folder is not None:
                folder = os.path.join(gpodder.downloads, self.download_folder)
                try:
                    os.rmdir(folder)
                except OSError:
                    logger.info("Old download folder is kept for %s", self.url)

            logger.info("Updating download_folder of %s to %s", self.url, download_folder)
            self.download_folder = download_folder
            self.save()

        save_dir = os.path.join(gpodder.downloads, self.download_folder)

        # Avoid encoding errors for OS-specific functions (bug 1570)
        save_dir = util.sanitize_encoding(save_dir)

        # Create save_dir if it does not yet exist
        if not util.make_directory(save_dir):
            logger.error("Could not create save_dir: %s", save_dir)

        return save_dir
Esempio n. 13
0
    def get_save_dir(self):
        save_dir=os.path.join(gl.downloaddir, self.filename, '')

        # Create save_dir if it does not yet exist
        if not util.make_directory( save_dir):
            log( 'Could not create save_dir: %s', save_dir, sender=self)

        return save_dir
Esempio n. 14
0
    def __init__(self,
                 config_class=config.Config,
                 database_class=dbsqlite.Database,
                 model_class=model.Model):
        # Initialize the gPodder home directory
        util.make_directory(gpodder.home)

        # Open the database and configuration file
        self.db = database_class(gpodder.database_file)
        self.model = model_class(self.db)
        self.config = config_class(gpodder.config_file)

        # Load extension modules and install the extension manager
        gpodder.user_extensions = extensions.ExtensionManager(self)

        # Load installed/configured plugins
        gpodder.load_plugins()

        # Update the current device in the configuration
        self.config.mygpo.device.type = util.detect_device_type()
Esempio n. 15
0
    def __init__(self,
                 config_class=config.Config,
                 database_class=dbsqlite.Database,
                 model_class=model.Model):
        # Initialize the gPodder home directory
        util.make_directory(gpodder.home)

        # Load hook modules and install the hook manager
        gpodder.user_hooks = hooks.HookManager()

        # Load installed/configured plugins
        gpodder.load_plugins()

        # Open the database and configuration file
        self.db = database_class(gpodder.database_file)
        self.model = model_class(self.db)
        self.config = config_class(gpodder.config_file)

        # Update the current device in the configuration
        self.config.mygpo_device_type = util.detect_device_type()
Esempio n. 16
0
    def get_save_dir(self):
        urldigest = hashlib.md5(self.url).hexdigest()
        sanitizedurl = util.sanitize_filename(self.url, self.MAX_FOLDERNAME_LENGTH)
        if self.foldername is None or (self.auto_foldername and (self.foldername == urldigest or self.foldername.startswith(sanitizedurl))):
            # we must change the folder name, because it has not been set manually
            fn_template = util.sanitize_filename(self.title, self.MAX_FOLDERNAME_LENGTH)

            # if this is an empty string, try the basename
            if len(fn_template) == 0:
                log('That is one ugly feed you have here! (Report this to bugs.gpodder.org: %s)', self.url, sender=self)
                fn_template = util.sanitize_filename(os.path.basename(self.url), self.MAX_FOLDERNAME_LENGTH)

            # If the basename is also empty, use the first 6 md5 hexdigest chars of the URL
            if len(fn_template) == 0:
                log('That is one REALLY ugly feed you have here! (Report this to bugs.gpodder.org: %s)', self.url, sender=self)
                fn_template = urldigest # no need for sanitize_filename here

            # Find a unique folder name for this podcast
            wanted_foldername = self.find_unique_folder_name(fn_template)

            # if the foldername has not been set, check if the (old) md5 filename exists
            if self.foldername is None and os.path.exists(os.path.join(self.download_dir, urldigest)):
                log('Found pre-0.15.0 download folder for %s: %s', self.title, urldigest, sender=self)
                self.foldername = urldigest

            # we have a valid, new folder name in "current_try" -> use that!
            if self.foldername is not None and wanted_foldername != self.foldername:
                # there might be an old download folder crawling around - move it!
                new_folder_name = os.path.join(self.download_dir, wanted_foldername)
                old_folder_name = os.path.join(self.download_dir, self.foldername)
                if os.path.exists(old_folder_name):
                    if not os.path.exists(new_folder_name):
                        # Old folder exists, new folder does not -> simply rename
                        log('Renaming %s => %s', old_folder_name, new_folder_name, sender=self)
                        os.rename(old_folder_name, new_folder_name)
                    else:
                        # Both folders exist -> move files and delete old folder
                        log('Moving files from %s to %s', old_folder_name, new_folder_name, sender=self)
                        for file in glob.glob(os.path.join(old_folder_name, '*')):
                            shutil.move(file, new_folder_name)
                        log('Removing %s', old_folder_name, sender=self)
                        shutil.rmtree(old_folder_name, ignore_errors=True)
            log('Updating foldername of %s to "%s".', self.url, wanted_foldername, sender=self)
            self.foldername = wanted_foldername
            self.save()

        save_dir = os.path.join(self.download_dir, self.foldername)

        # Create save_dir if it does not yet exist
        if not util.make_directory( save_dir):
            log( 'Could not create save_dir: %s', save_dir, sender = self)

        return save_dir
Esempio n. 17
0
    def __init__(self,
                 config_class=config.Config,
                 database_class=dbsqlite.Database,
                 model_class=model.Model):
        # Initialize the gPodder home directory
        util.make_directory(gpodder.home)

        # Open the database and configuration file
        self.db = database_class(gpodder.database_file)
        self.model = model_class(self.db)
        self.config = config_class(gpodder.config_file)

        # Load extension modules and install the extension manager
        gpodder.user_extensions = extensions.ExtensionManager(self)

        # Load installed/configured plugins
        gpodder.load_plugins()

        # Update the current device in the configuration
        self.config.mygpo.device.type = util.detect_device_type()

        # Initialize Flattr integration
        self.flattr = flattr.Flattr(self.config.flattr)
Esempio n. 18
0
 def write_m3u(self, episodes):
     """
     write the list into the playlist on the device
     """
     logger.info('Writing playlist file: %s', self.playlist_file)
     if not util.make_directory(self.playlist_folder):
         raise IOError(_('Folder %s could not be created.') % self.playlist_folder, _('Error writing playlist'))
     else:
         fp = open(os.path.join(self.playlist_folder, self.playlist_file), 'w')
         fp.write('#EXTM3U%s' % self.linebreak)
         for current_episode in episodes:
             filename = self.get_filename_for_playlist(current_episode)
             fp.write(self.build_extinf(filename))
             filename = self.get_absolute_filename_for_playlist(current_episode)
             fp.write(filename)
             fp.write(self.linebreak)
         fp.close()
Esempio n. 19
0
 def write_m3u(self, episodes):
     """
     write the list into the playlist on the device
     """
     logger.info('Writing playlist file: %s', self.playlist_file)
     if not util.make_directory(self.playlist_folder):
         raise IOError(
             _('Folder %s could not be created.') % self.playlist_folder,
             _('Error writing playlist'))
     else:
         fp = open(os.path.join(self.playlist_folder, self.playlist_file),
                   'w')
         fp.write('#EXTM3U%s' % self.linebreak)
         for current_episode in episodes:
             filename = self.get_filename_for_playlist(current_episode)
             fp.write(self.build_extinf(filename))
             filename = self.get_absolute_filename_for_playlist(
                 current_episode)
             fp.write(filename)
             fp.write(self.linebreak)
         fp.close()
Esempio n. 20
0
 def write_m3u(self):
     """
     write the list into the playlist on the device
     """
     log('Writing playlist file: %s', self.playlist_file, sender=self)
     playlist_folder = os.path.split(self.playlist_file)[0]
     if not util.make_directory(playlist_folder):
         self.show_message(_('Folder %s could not be created.') % playlist_folder, _('Error writing playlist'))
     else:
         try:
             fp = open(self.playlist_file, 'w')
             fp.write('#EXTM3U%s' % self.linebreak)
             for icon, checked, filename in self.playlist:
                 fp.write(self.build_extinf(filename))
                 if not checked:
                     fp.write('#')
                 fp.write(filename)
                 fp.write(self.linebreak)
             fp.close()
             self.show_message(_('The playlist on your MP3 player has been updated.'), _('Update successful'))
         except IOError, ioe:
             self.show_message(str(ioe), _('Error writing playlist file'))
 def write_m3u(self, episodes):
     """
     write the list into the playlist on the device
     """
     logger.info('Writing playlist file: %s', self.playlist_file)
     if not util.make_directory(self.playlist_folder):
         raise IOError(_('Folder %s could not be created.') % self.playlist_folder, _('Error writing playlist'))
     else:
         fp = open(os.path.join(self.playlist_folder, self.playlist_file), 'w')
         fp.write('#EXTM3U%s' % self.linebreak)
         for current_episode in episodes:
             filename_base = util.sanitize_filename(current_episode.sync_filename(
                 self._config.device_sync.custom_sync_name_enabled,
                 self._config.device_sync.custom_sync_name),
                 self._config.device_sync.max_filename_length)
             filename = filename_base + os.path.splitext(current_episode.local_filename(create=False))[1].lower()
             filename = self.get_filename_for_playlist(current_episode)
             fp.write(self.build_extinf(filename))
             filename = self.get_absolute_filename_for_playlist(current_episode)
             fp.write(filename)
             fp.write(self.linebreak)
         fp.close()
Esempio n. 22
0
 def get_download_dir( self):
     util.make_directory( self.config.download_dir)
     return self.config.download_dir
Esempio n. 23
0
    def check_download_folder(self):
        """Check the download folder for externally-downloaded files

        This will try to assign downloaded files with episodes in the
        database and (failing that) will move downloaded files into
        the "Unknown" subfolder in the download directory, so that
        the user knows that gPodder doesn't know to which episode the
        file belongs (the "Unknown" folder may be used by external
        tools or future gPodder versions for better import support).

        This will also cause missing files to be marked as deleted.
        """
        known_files = set()

        for episode in self.get_downloaded_episodes():
            if episode.was_downloaded():
                filename = episode.local_filename(create=False)
                if not os.path.exists(filename):
                    # File has been deleted by the user - simulate a
                    # delete event (also marks the episode as deleted)
                    logger.debug('Episode deleted: %s', filename)
                    episode.delete_from_disk()
                    continue

                known_files.add(filename)

        existing_files = set(filename for filename in \
                glob.glob(os.path.join(self.save_dir, '*')) \
                if not filename.endswith('.partial'))
        external_files = existing_files.difference(list(known_files) + \
                [os.path.join(self.save_dir, x) \
                for x in ('folder.jpg', 'Unknown')])
        if not external_files:
            return

        all_episodes = self.get_all_episodes()

        for filename in external_files:
            found = False

            basename = os.path.basename(filename)
            existing = [e for e in all_episodes if e.download_filename == basename]
            if existing:
                existing = existing[0]
                logger.info('Importing external download: %s', filename)
                existing.on_downloaded(filename)
                continue

            for episode in all_episodes:
                wanted_filename = episode.local_filename(create=True, \
                        return_wanted_filename=True)
                if basename == wanted_filename:
                    logger.info('Importing external download: %s', filename)
                    episode.download_filename = basename
                    episode.on_downloaded(filename)
                    found = True
                    break

                wanted_base, wanted_ext = os.path.splitext(wanted_filename)
                target_base, target_ext = os.path.splitext(basename)
                if wanted_base == target_base:
                    # Filenames only differ by the extension
                    wanted_type = util.file_type_by_extension(wanted_ext)
                    target_type = util.file_type_by_extension(target_ext)

                    # If wanted type is None, assume that we don't know
                    # the right extension before the download (e.g. YouTube)
                    # if the wanted type is the same as the target type,
                    # assume that it's the correct file
                    if wanted_type is None or wanted_type == target_type:
                        logger.info('Importing external download: %s', filename)
                        episode.download_filename = basename
                        episode.on_downloaded(filename)
                        found = True
                        break

            if not found:
                logger.warn('Unknown external file: %s', filename)
                target_dir = os.path.join(self.save_dir, 'Unknown')
                if util.make_directory(target_dir):
                    target_file = os.path.join(target_dir, basename)
                    logger.info('Moving %s => %s', filename, target_file)
                    try:
                        shutil.move(filename, target_file)
                    except Exception, e:
                        logger.error('Could not move file: %s', e, exc_info=True)