def __reset_database(self): """ Reset database """ def update_ui(): App().window.container.go_home() App().scanner.update(ScanType.FULL) App().player.stop() if App().ws_director.collection_ws is not None: App().ws_director.collection_ws.stop() uris = App().tracks.get_uris() i = 0 SqlCursor.add(App().db) SqlCursor.add(self.__history) count = len(uris) for uri in uris: self.del_from_db(uri, True) self.__update_progress(i, count, 0.01) i += 1 App().tracks.del_persistent(False) App().tracks.clean(False) App().albums.clean(False) App().artists.clean(False) App().genres.clean(False) App().cache.clear_table("duration") SqlCursor.commit(App().db) SqlCursor.remove(App().db) SqlCursor.commit(self.__history) SqlCursor.remove(self.__history) GLib.idle_add(update_ui)
def upgrade(self, db): """ Upgrade db @param db as Database """ version = 0 SqlCursor.add(db) with SqlCursor(db, True) as sql: result = sql.execute("PRAGMA user_version") v = result.fetchone() if v is not None: version = v[0] if version < self.version: for i in range(version + 1, self.version + 1): try: if isinstance(self._UPGRADES[i], str): sql.execute(self._UPGRADES[i]) SqlCursor.commit(db) else: self._UPGRADES[i](db) SqlCursor.commit(db) except Exception as e: Logger.error("DB upgrade %s failed: %s" % (i, e)) sql.execute("PRAGMA user_version=%s" % self.version) SqlCursor.remove(db)
def del_from_db(self, uri, backup): """ Delete track from db @param uri as str @param backup as bool @return (popularity, ltime, mtime, loved album, album_popularity) """ try: track_id = App().tracks.get_id_by_uri(uri) duration = App().tracks.get_duration(track_id) album_id = App().tracks.get_album_id(track_id) album_artist_ids = App().albums.get_artist_ids(album_id) artist_ids = App().tracks.get_artist_ids(track_id) track_pop = App().tracks.get_popularity(track_id) track_rate = App().tracks.get_rate(track_id) track_ltime = App().tracks.get_ltime(track_id) album_mtime = App().tracks.get_mtime(track_id) track_loved = App().tracks.get_loved(track_id) album_pop = App().albums.get_popularity(album_id) album_rate = App().albums.get_rate(album_id) album_loved = App().albums.get_loved(album_id) album_synced = App().albums.get_synced(album_id) if backup: f = Gio.File.new_for_uri(uri) name = f.get_basename() self.__history.add(name, duration, track_pop, track_rate, track_ltime, album_mtime, track_loved, album_loved, album_pop, album_rate, album_synced) App().tracks.remove(track_id) genre_ids = App().tracks.get_genre_ids(track_id) App().albums.clean() App().genres.clean() App().artists.clean() App().cache.clear_durations(album_id) SqlCursor.commit(App().db) item = CollectionItem(album_id=album_id) if not App().albums.get_name(album_id): item.artist_ids = [] for artist_id in album_artist_ids + artist_ids: if not App().artists.get_name(artist_id): item.artist_ids.append(artist_id) item.genre_ids = [] for genre_id in genre_ids: if not App().genres.get_name(genre_id): item.genre_ids.append(genre_id) emit_signal(self, "updated", item, ScanUpdate.REMOVED) else: # Force genre for album genre_ids = App().tracks.get_album_genre_ids(album_id) App().albums.set_genre_ids(album_id, genre_ids) emit_signal(self, "updated", item, ScanUpdate.MODIFIED) return (track_pop, track_rate, track_ltime, album_mtime, track_loved, album_loved, album_pop, album_rate) except Exception as e: Logger.error("CollectionScanner::del_from_db: %s" % e)
def __notify_ui(self, items): """ Notify UI based on current items @param items as [CollectionItem] """ SqlCursor.commit(App().db) for item in items: if item.new_album: emit_signal(self, "updated", item, ScanUpdate.ADDED) else: emit_signal(self, "updated", item, ScanUpdate.MODIFIED)
def __del_from_db(self, uri): """ Delete track from db @param uri as str """ try: f = Gio.File.new_for_uri(uri) name = f.get_basename() track_id = App().tracks.get_id_by_uri(uri) album_id = App().tracks.get_album_id(track_id) genre_ids = App().tracks.get_genre_ids(track_id) album_artist_ids = App().albums.get_artist_ids(album_id) artist_ids = App().tracks.get_artist_ids(track_id) popularity = App().tracks.get_popularity(track_id) rate = App().tracks.get_rate(track_id) ltime = App().tracks.get_ltime(track_id) mtime = App().tracks.get_mtime(track_id) loved_track = App().tracks.get_loved(track_id) duration = App().tracks.get_duration(track_id) album_popularity = App().albums.get_popularity(album_id) album_rate = App().albums.get_rate(album_id) loved_album = App().albums.get_loved(album_id) uri = App().tracks.get_uri(track_id) self.__history.add(name, duration, popularity, rate, ltime, mtime, loved_track, loved_album, album_popularity, album_rate) App().tracks.remove(track_id) App().tracks.clean(track_id) cleaned = App().albums.clean(album_id) if cleaned: SqlCursor.commit(App().db) GLib.idle_add(self.emit, "album-updated", album_id, True) for artist_id in album_artist_ids + artist_ids: cleaned = App().artists.clean(artist_id) # Force update even if not cleaned as artist may # have been removed from a selected genre GLib.idle_add(self.emit, "artist-updated", artist_id, False) for genre_id in genre_ids: cleaned = App().genres.clean(genre_id) if cleaned: SqlCursor.commit(App().db) GLib.idle_add(self.emit, "genre-updated", genre_id, False) except Exception as e: Logger.error("CollectionScanner::__del_from_db: %s" % e)
def __add2db(self, uri, mtime): """ Add new file to db with information @param uri as string @param mtime as int @return track id as int @warning, be sure SqlCursor is available for App().db """ f = Gio.File.new_for_uri(uri) Logger.debug("CollectionScanner::add2db(): Read tags") info = self.get_info(uri) tags = info.get_tags() name = f.get_basename() title = self.get_title(tags, name) version = self.get_version(tags) artists = self.get_artists(tags) composers = self.get_composers(tags) performers = self.get_performers(tags) a_sortnames = self.get_artist_sortnames(tags) aa_sortnames = self.get_album_artist_sortnames(tags) album_artists = self.get_album_artists(tags) album_name = self.get_album_name(tags) mb_album_id = self.get_mb_album_id(tags) mb_track_id = self.get_mb_track_id(tags) genres = self.get_genres(tags) discnumber = self.get_discnumber(tags) discname = self.get_discname(tags) tracknumber = self.get_tracknumber(tags, name) (year, timestamp) = self.get_original_year(tags) if year is None: (year, timestamp) = self.get_year(tags) duration = int(info.get_duration() / 1000000000) if version != "": title += " (%s)" % version # If no artists tag, use album artist if artists == "": artists = album_artists # if artists is always null, no album artists too, # use composer/performer if artists == "": artists = performers album_artists = composers if artists == "": artists = album_artists if artists == "": artists = _("Unknown") Logger.debug("CollectionScanner::add2db(): Restore stats") # Restore stats (track_pop, track_rate, track_ltime, album_mtime, track_loved, album_loved, album_pop, album_rate) = self.__history.get(name, duration) # If nothing in stats, use track mtime if album_mtime == 0: album_mtime = mtime Logger.debug("CollectionScanner::add2db(): Add artists %s" % artists) artist_ids = self.add_artists(artists, a_sortnames) Logger.debug("CollectionScanner::add2db(): " "Add album artists %s" % album_artists) album_artist_ids = self.add_album_artists(album_artists, aa_sortnames) # User does not want compilations if self.__disable_compilations and not album_artist_ids: album_artist_ids = artist_ids missing_artist_ids = list(set(album_artist_ids) - set(artist_ids)) # https://github.com/gnumdk/lollypop/issues/507#issuecomment-200526942 # Special case for broken tags # Can't do more because don't want to break split album behaviour if len(missing_artist_ids) == len(album_artist_ids): artist_ids += missing_artist_ids Logger.debug("CollectionScanner::add2db(): Add album: " "%s, %s" % (album_name, album_artist_ids)) album_id = self.add_album(album_name, mb_album_id, album_artist_ids, uri, album_loved, album_pop, album_rate, mtime) genre_ids = self.add_genres(genres) # Add track to db Logger.debug("CollectionScanner::add2db(): Add track") track_id = App().tracks.add(title, uri, duration, tracknumber, discnumber, discname, album_id, year, timestamp, track_pop, track_rate, track_loved, track_ltime, mtime, mb_track_id) Logger.debug("CollectionScanner::add2db(): Update track") self.__update_track(track_id, artist_ids, genre_ids) Logger.debug("CollectionScanner::add2db(): Update album") SqlCursor.commit(App().db) self.__update_album(album_id, album_artist_ids, genre_ids, year, timestamp) SqlCursor.commit(App().db) for genre_id in genre_ids: GLib.idle_add(self.emit, "genre-updated", genre_id, True) return track_id
def __scan(self, uris, saved): """ Scan music collection for music files @param uris as [str] @param saved as bool @thread safe """ modifications = False if self.__history is None: self.__history = History() mtimes = App().tracks.get_mtimes() (new_tracks, new_dirs) = self.__get_objects_for_uris(uris) orig_tracks = App().tracks.get_uris() was_empty = len(orig_tracks) == 0 count = len(new_tracks) + len(orig_tracks) # Add monitors on dirs if self.__inotify is not None: for d in new_dirs: if d.startswith("file://"): self.__inotify.add_monitor(d) i = 0 # Look for new files/modified file SqlCursor.add(App().db) try: to_add = [] for uri in new_tracks: if self.__thread is None: SqlCursor.remove(App().db) return try: GLib.idle_add(self.__update_progress, i, count) f = Gio.File.new_for_uri(uri) # We do not use time::modified because many tag editors # just preserve this setting try: info = f.query_info("time::changed", Gio.FileQueryInfoFlags.NONE, None) mtime = int( info.get_attribute_as_string("time::changed")) except: # Fallback for remote fs info = f.query_info("time::modified", Gio.FileQueryInfoFlags.NONE, None) mtime = int( info.get_attribute_as_string("time::modified")) # If songs exists and mtime unchanged, continue, # else rescan if uri in orig_tracks: orig_tracks.remove(uri) i += 1 if not saved or mtime <= mtimes.get(uri, mtime + 1): i += 1 continue else: SqlCursor.allow_thread_execution(App().db) self.__del_from_db(uri) # If not saved, use 0 as mtime, easy delete on quit if not saved: mtime = 0 # On first scan, use modification time # Else, use current time elif not was_empty: mtime = int(time()) to_add.append((uri, mtime)) except Exception as e: Logger.error("CollectionScanner::__scan(mtime): %s" % e) if to_add or orig_tracks: modifications = True # Clean deleted files # Now because we need to populate history # Only if we are saving if saved: for uri in orig_tracks: i += 1 GLib.idle_add(self.__update_progress, i, count) self.__del_from_db(uri) SqlCursor.allow_thread_execution(App().db) # Add files to db for (uri, mtime) in to_add: try: Logger.debug("Adding file: %s" % uri) i += 1 GLib.idle_add(self.__update_progress, i, count) self.__add2db(uri, mtime) SqlCursor.allow_thread_execution(App().db) except Exception as e: Logger.error("CollectionScanner::__scan(add): %s, %s" % (e, uri)) except Exception as e: Logger.error("CollectionScanner::__scan(): %s" % e) SqlCursor.commit(App().db) SqlCursor.remove(App().db) GLib.idle_add(self.__finish, modifications and saved) if not saved: self.__play_new_tracks(new_tracks) del self.__history self.__history = None
def __scan_files(self, files, db_uris, scan_type): """ Scan music collection for new audio files @param files as [str] @param db_uris as [str] @param scan_type as ScanType @return new track uris as [str] @thread safe """ SqlCursor.add(App().db) i = 0 # New tracks present in collection new_tracks = [] # Get mtime of all tracks to detect which has to be updated db_mtimes = App().tracks.get_mtimes() count = len(files) + 1 try: # Scan new files for (mtime, uri) in files: # Handle a stop request if self.__thread is None and scan_type != ScanType.EPHEMERAL: raise Exception("Scan add cancelled") try: if not self.__scan_to_handle(uri): continue if mtime > db_mtimes.get(uri, 0): # If not saved, use 0 as mtime, easy delete on quit if scan_type == ScanType.EPHEMERAL: mtime = 0 # Do not use mtime if not intial scan elif db_mtimes: mtime = int(time()) Logger.debug("Adding file: %s" % uri) self.__add2db(uri, mtime) SqlCursor.allow_thread_execution(App().db) new_tracks.append(uri) except Exception as e: Logger.error("CollectionScanner:: __scan_add_files: % s" % e) i += 1 self.__update_progress(i, count) if scan_type != ScanType.EPHEMERAL and self.__thread is not None: # We need to check files are always in collections if scan_type == ScanType.FULL: collections = App().settings.get_music_uris() else: collections = None for uri in db_uris: # Handle a stop request if self.__thread is None: raise Exception("Scan del cancelled") in_collection = True if collections is not None: in_collection = False for collection in collections: if collection in uri: in_collection = True break f = Gio.File.new_for_uri(uri) if not in_collection or not f.query_exists(): self.del_from_db(uri, True) SqlCursor.allow_thread_execution(App().db) except Exception as e: Logger.warning("CollectionScanner:: __scan_files: % s" % e) SqlCursor.commit(App().db) SqlCursor.remove(App().db) return new_tracks
def save_track(self, genres, artists, a_sortnames, mb_artist_id, album_artists, aa_sortnames, mb_album_artist_id, album_name, mb_album_id, uri, album_loved, album_pop, album_rate, album_synced, album_mtime, title, duration, tracknumber, discnumber, discname, year, timestamp, track_mtime, track_pop, track_rate, track_loved, track_ltime, mb_track_id, bpm): """ Add track to DB @param genres as str/None @param artists as str @param a_sortnames as str @param mb_artist_id as str @param album_artists as str @param aa_sortnames as str @param mb_album_artist_id as str @param album_name as str @param mb_album_id as str @param uri as str @param album_loved as int @param album_pop as int @param album_rate as int @param album_synced as int @param album_mtime as int @param title as str @param duration as int @param tracknumber as int @param discnumber as int @param discname as str @param year as int @param timestamp as int @param track_mtime as int @param track_pop as int @param track_rate as int @param track_loved as int @param track_ltime as int @param mb_track_id as str @param bpm as int """ Logger.debug("CollectionScanner::save_track(): Add artists %s" % artists) artist_ids = self.add_artists(artists, a_sortnames, mb_artist_id) Logger.debug("CollectionScanner::save_track(): " "Add album artists %s" % album_artists) album_artist_ids = self.add_artists(album_artists, aa_sortnames, mb_album_artist_id) # User does not want compilations if self.__disable_compilations and not album_artist_ids: album_artist_ids = artist_ids missing_artist_ids = list(set(album_artist_ids) - set(artist_ids)) # https://github.com/gnumdk/lollypop/issues/507#issuecomment-200526942 # Special case for broken tags # Can't do more because don't want to break split album behaviour if len(missing_artist_ids) == len(album_artist_ids): artist_ids += missing_artist_ids Logger.debug("CollectionScanner::save_track(): Add album: " "%s, %s" % (album_name, album_artist_ids)) (album_added, album_id) = self.add_album(album_name, mb_album_id, album_artist_ids, uri, album_loved, album_pop, album_rate, album_synced, album_mtime) if genres is None: genre_ids = [Type.WEB] else: genre_ids = self.add_genres(genres) # Add track to db Logger.debug("CollectionScanner::save_track(): Add track") track_id = App().tracks.add(title, uri, duration, tracknumber, discnumber, discname, album_id, year, timestamp, track_pop, track_rate, track_loved, track_ltime, track_mtime, mb_track_id, bpm) Logger.debug("CollectionScanner::save_track(): Update track") self.update_track(track_id, artist_ids, genre_ids) Logger.debug("CollectionScanner::save_track(): Update album") SqlCursor.commit(App().db) self.update_album(album_id, album_artist_ids, genre_ids, year, timestamp) SqlCursor.commit(App().db) for genre_id in genre_ids: # Be sure to not send Type.WEB if genre_id >= 0: GLib.idle_add(self.emit, "genre-updated", genre_id, True) if album_added: GLib.idle_add(self.emit, "album-updated", album_id, True) return (track_id, album_id)