Commit 7079e6d5 authored by Cédric Bellegarde's avatar Cédric Bellegarde

Backup web tracks for restore on reset. Fix #1807

parent fb99f646
Pipeline #81763 passed with stage
in 51 minutes and 20 seconds
......@@ -15,6 +15,7 @@ from lollypop.art_album import AlbumArt
from lollypop.art_radio import RadioArt
from lollypop.logger import Logger
from lollypop.downloader import Downloader
from lollypop.utils import create_dir
from shutil import rmtree
......@@ -32,9 +33,9 @@ class Art(BaseArt, AlbumArt, RadioArt, Downloader):
AlbumArt.__init__(self)
RadioArt.__init__(self)
Downloader.__init__(self)
self._create_dir(self._CACHE_PATH)
self._create_dir(self._STORE_PATH)
self._create_dir(self._WEB_PATH)
create_dir(self._CACHE_PATH)
create_dir(self._STORE_PATH)
create_dir(self._WEB_PATH)
def clean_web(self):
"""
......@@ -51,6 +52,6 @@ class Art(BaseArt, AlbumArt, RadioArt, Downloader):
"""
try:
rmtree(self._CACHE_PATH)
self._create_dir(self._CACHE_PATH)
create_dir(self._CACHE_PATH)
except Exception as e:
Logger.error("Art::clean_all_cache(): %s", e)
......@@ -24,7 +24,7 @@ class BaseArt(GObject.GObject):
# Fallback when album dir is readonly
_STORE_PATH = GLib.get_user_data_dir() + "/lollypop/store"
# Store for Web
_WEB_PATH = GLib.get_user_data_dir() + "/lollypop/web"
_WEB_PATH = GLib.get_user_data_dir() + "/lollypop/web_store"
__gsignals__ = {
"album-artwork-changed": (GObject.SignalFlags.RUN_FIRST, None, (int,)),
"artist-artwork-changed": (GObject.SignalFlags.RUN_FIRST,
......@@ -123,18 +123,6 @@ class BaseArt(GObject.GObject):
0, 0)
return new_pixbuf
def _create_dir(self, path):
"""
Create store dir
@param path as str
"""
d = Gio.File.new_for_path(path)
if not d.query_exists():
try:
d.make_directory_with_parents()
except:
Logger.info("Can't create %s" % path)
#######################
# PRIVATE #
#######################
......
......@@ -21,14 +21,15 @@ from gi.repository.Gio import FILE_ATTRIBUTE_STANDARD_NAME, \
from gettext import gettext as _
from threading import Thread
from time import time
import json
from lollypop.inotify import Inotify
from lollypop.define import App, ScanType
from lollypop.define import App, ScanType, Type
from lollypop.sqlcursor import SqlCursor
from lollypop.tagreader import TagReader
from lollypop.logger import Logger
from lollypop.database_history import History
from lollypop.utils import is_audio, is_pls, get_mtime, profile
from lollypop.utils import is_audio, is_pls, get_mtime, profile, create_dir
SCAN_QUERY_INFO = "{},{},{},{}".format(FILE_ATTRIBUTE_STANDARD_NAME,
......@@ -48,6 +49,8 @@ class CollectionScanner(GObject.GObject, TagReader):
"album-updated": (GObject.SignalFlags.RUN_FIRST, None, (int, bool))
}
_WEB_COLLECTION = GLib.get_user_data_dir() + "/lollypop/web_collection"
def __init__(self):
"""
Init collection scanner
......@@ -63,6 +66,7 @@ class CollectionScanner(GObject.GObject, TagReader):
else:
self.__inotify = None
App().albums.update_max_count()
create_dir(self._WEB_COLLECTION)
def update(self, scan_type, uris=[]):
"""
......@@ -125,6 +129,95 @@ class CollectionScanner(GObject.GObject, TagReader):
timestamp = App().tracks.get_timestamp_for_album(album_id)
App().albums.set_timestamp(album_id, timestamp)
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_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_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_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:
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)
def update_track(self, track_id, artist_ids, genre_ids):
"""
Set track artists/genres
......@@ -245,6 +338,38 @@ class CollectionScanner(GObject.GObject, TagReader):
if d.startswith("file://"):
self.__inotify.add_monitor(d)
def __import_web_tracks(self):
"""
Import locally saved web tracks
"""
try:
# Directly add files, walk through directories
f = Gio.File.new_for_path(self._WEB_COLLECTION)
infos = f.enumerate_children(SCAN_QUERY_INFO,
Gio.FileQueryInfoFlags.NONE,
None)
for info in infos:
f = infos.get_child(info)
if info.get_is_hidden():
continue
elif info.get_file_type() == Gio.FileType.DIRECTORY:
pass
else:
(status, content, tag) = f.load_contents()
data = json.loads(content)
self.save_track(
None, ",".join(data["artists"]), "", "",
",".join(data["album_artists"]),
"", "", data["album_name"], "", data["uri"],
data["album_loved"], data["album_popularity"],
data["album_rate"], -1, data["title"], data["duration"],
data["tracknumber"], data["discnumber"],
data["discname"], data["year"], data["timestamp"], -1,
data["track_popularity"], data["track_rate"],
data["track_loved"], 0, "", 0)
except Exception as e:
Logger.error("CollectionScanner::__import_web_tracks(): %s", e)
@profile
def __get_objects_for_uris(self, scan_type, uris):
"""
......@@ -306,6 +431,9 @@ class CollectionScanner(GObject.GObject, TagReader):
@param uris as [str]
@thread safe
"""
if not App().tracks.get_mtimes():
self.__import_web_tracks()
(files, dirs) = self.__get_objects_for_uris(scan_type, uris)
if files is None:
......@@ -415,11 +543,11 @@ class CollectionScanner(GObject.GObject, TagReader):
SqlCursor.remove(App().db)
return new_tracks
def __add2db(self, uri, mtime):
def __add2db(self, uri, track_mtime):
"""
Add new file(or update one) to db with information
@param uri as string
@param mtime as int
@param track_mtime as int
@return track id as int
@warning, be sure SqlCursor is available for App().db
"""
......@@ -492,51 +620,14 @@ class CollectionScanner(GObject.GObject, TagReader):
track_rate = track_popm
# 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, mb_artist_id)
Logger.debug("CollectionScanner::add2db(): "
"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::add2db(): 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, 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, bpm)
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)
if album_added:
GLib.idle_add(self.emit, "album-updated", album_id, True)
album_mtime = track_mtime
(track_id, album_id) = self.save_track(
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_mtime, title, duration, tracknumber,
discnumber, discname, year, timestamp, track_mtime,
track_pop, track_rate, track_loved, track_ltime,
mb_track_id, bpm)
return track_id
......@@ -620,6 +620,34 @@ class TracksDatabase:
return v[0]
return 0
def get_discnumber(self, track_id):
"""
Get disc number for track id
@param track id as int
@return discnumber as int
"""
with SqlCursor(App().db) as sql:
result = sql.execute("SELECT discnumber FROM tracks\
WHERE rowid=?", (track_id,))
v = result.fetchone()
if v is not None:
return v[0]
return 0
def get_discname(self, track_id):
"""
Get disc name for track id
@param track id as int
@return discname as str
"""
with SqlCursor(App().db) as sql:
result = sql.execute("SELECT discname FROM tracks\
WHERE rowid=?", (track_id,))
v = result.fetchone()
if v is not None:
return v[0]
return ""
def get_duration(self, track_id):
"""
Get track duration for track id
......
......@@ -16,12 +16,10 @@ import json
from base64 import b64encode
from time import time, sleep
from lollypop.sqlcursor import SqlCursor
from lollypop.tagreader import TagReader
from lollypop.logger import Logger
from lollypop.objects import Album
from lollypop.helper_task import TaskHelper
from lollypop.define import SPOTIFY_CLIENT_ID, SPOTIFY_SECRET, App, Type
from lollypop.define import SPOTIFY_CLIENT_ID, SPOTIFY_SECRET, App
class SpotifyHelper(GObject.Object):
......@@ -360,7 +358,6 @@ class SpotifyHelper(GObject.Object):
@param payload as {}
@return track_id
"""
t = TagReader()
title = payload["name"]
_artists = []
for artist in payload["artists"]:
......@@ -386,52 +383,12 @@ class SpotifyHelper(GObject.Object):
timestamp = None
year = None
duration = payload["duration_ms"] // 1000
mb_album_id = mb_track_id = None
a_sortnames = aa_sortnames = ""
cover_uri = payload["album"]["images"][1]["url"]
uri = "web://%s" % payload["id"]
Logger.debug("SpotifyHelper::__save_track(): Add artists %s" % artists)
artist_ids = t.add_artists(artists, a_sortnames)
Logger.debug("SpotifyHelper::__save_track(): "
"Add album artists %s" % album_artists)
album_artist_ids = t.add_artists(album_artists, aa_sortnames)
# User does not want compilations
if not App().settings.get_value("show-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("SpotifyHelper::__save_track(): Add album: "
"%s, %s" % (album_name, album_artist_ids))
(added, album_id) = t.add_album(album_name, mb_album_id,
album_artist_ids,
"", False, 0, 0, 0)
genre_ids = [Type.WEB]
# Add track to db
Logger.debug("SpotifyHelper::__save_track(): Add track")
track_id = App().tracks.get_id_by(title, album_id)
# Track already in DB
if track_id is not None:
return (album_id, track_id, cover_uri)
track_id = App().tracks.add(title, uri, duration,
tracknumber, discnumber, discname,
album_id, year, timestamp, 0,
0, False, 0,
0, mb_track_id, 0)
Logger.debug("SpotifyHelper::__save_track(): Update track")
App().scanner.update_track(track_id, artist_ids, genre_ids)
Logger.debug("SpotifyHelper::__save_track(): Update album")
SqlCursor.commit(App().db)
App().scanner.update_album(album_id, album_artist_ids,
genre_ids, year, timestamp)
SqlCursor.commit(App().db)
(track_id, album_id) = App().scanner.save_track(
None, artists, "", "", album_artists, "", "",
album_name, None, uri, 0, 0,
0, 0, title, duration, tracknumber,
discnumber, discname, year, timestamp, 0,
0, 0, 0, 0, "", 0)
return (album_id, track_id, cover_uri)
......@@ -11,13 +11,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from gi.repository import GLib
from gi.repository import GLib, Gio
import json
from urllib.parse import urlparse
from lollypop.radios import Radios
from lollypop.logger import Logger
from lollypop.define import App, Type
from lollypop.utils import remove_static
from lollypop.utils import remove_static, escape
class Base:
......@@ -230,6 +232,7 @@ class Album(Base):
"uri": "",
"tracks_count": 1,
"duration": 0,
"popularity": 0,
"mtime": 1,
"synced": False,
"loved": False,
......@@ -437,6 +440,8 @@ class Track(Base):
"genres": [],
"duration": 0,
"number": 0,
"discnumber": 0,
"discname": "",
"year": None,
"timestamp": None,
"mtime": 1,
......@@ -524,13 +529,48 @@ class Track(Base):
def save(self, save):
"""
Save track to collection
Cache it to Web Collection (for restore on reset)
@param save as bool
"""
if save:
App().tracks.set_mtime(self.id, -1)
else:
App().tracks.set_mtime(self.id, 0)
self.reset("mtime")
try:
filename = "%s_%s_%s" % (self.album.name, self.artists, self.name)
filepath = "%s/%s.txt" % (App().scanner._WEB_COLLECTION,
escape(filename))
f = Gio.File.new_for_path(filepath)
if save:
App().tracks.set_mtime(self.id, -1)
data = {
"title": self.name,
"album_name": self.album.name,
"artists": self.artists,
"album_artists": self.album.artists,
"album_loved": self.album.loved,
"album_popularity": self.album.popularity,
"album_rate": self.album.get_rate(),
"discnumber": self.discnumber,
"discname": self.discname,
"duration": self.duration,
"tracknumber": App().tracks.get_number(self.id),
"track_popularity": self.popularity,
"track_loved": self.loved,
"track_rate": self.get_rate(),
"year": self.year,
"timestamp": self.timestamp,
"uri": self.uri
}
content = json.dumps(data).encode("utf-8")
fstream = f.replace(None, False,
Gio.FileCreateFlags.REPLACE_DESTINATION,
None)
if fstream is not None:
fstream.write(content, None)
fstream.close()
else:
App().tracks.set_mtime(self.id, 0)
f.delete()
self.reset("mtime")
except Exception as e:
Logger.error("Track::save(): %s", e)
def get_featuring_artist_ids(self, album_artist_ids):
"""
......
......@@ -351,6 +351,19 @@ def is_readonly(uri):
return True
def create_dir(path):
"""
Create dir
@param path as str
"""
d = Gio.File.new_for_path(path)
if not d.query_exists():
try:
d.make_directory_with_parents()
except:
Logger.info("Can't create %s" % path)
def remove_static(ids):
"""
Remove static ids
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment