From 286f605e5ca35150d94a606ae31435fe83a9fa99 Mon Sep 17 00:00:00 2001 From: Ben Klopfenstein Date: Mon, 8 Jan 2018 12:18:04 -0700 Subject: [PATCH 1/3] translate russian so python will load without a declared encoding --- gmusicfs/gmusicfs.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gmusicfs/gmusicfs.py b/gmusicfs/gmusicfs.py index 898d5e9..55538c1 100644 --- a/gmusicfs/gmusicfs.py +++ b/gmusicfs/gmusicfs.py @@ -321,7 +321,7 @@ def __init__(self, library, data): self.__rendered_tag = bytes() self.__tag = "" - # TODO: надо разобраться где это юзается + # TODO: I need to figure out where this is used # track_id if 'track' in data: self.__id = data['trackId'] @@ -484,10 +484,10 @@ def read(self, read_offset, read_chunk_size): print("RUN READ: offset:" + str(read_offset) + "; size: " + str(read_chunk_size)) - # костыль + # Crutch estimated_size_test = 128*1024 # test 128kb def size if 'estimatedSize' not in self.__data: - print("ИНЖАЛИД ТРАК, НАХЕР ЕГО") + print("INVALID TRACK") return # TODO: skip invalid tracks if not self.__tag: # Crating tag only when needed WTAF @@ -513,20 +513,20 @@ def read(self, read_offset, read_chunk_size): print("###") #return None - # если читаем конец файла (последние 4*4к) и трек на половину не загружен, - # то шлём к херам собачьим того кто читает + # If we read the end of the file (the last 4 * 4k) and the track is not half loaded, + # then we send to the hears of the one who reads if read_offset + read_chunk_size >= int(self.__data['estimatedSize'])-5*4096 \ and len(self.__stream_cache)-tag_length < (int(self.__data['estimatedSize']) / 2 ): print("\033[032moffset: \t%10.3fK\033[0m" % (read_offset / 1024)) print("estSize: \t%10.3fK" % (int(self.__data['estimatedSize'])/1024)) - print("Хрен тебе! читай последовательно!") + print("Read Consistently!") return None # TODO: need test slow connection pos = (read_offset + read_chunk_size) - # костыль + # Crutch print("\n############ debugPos") print("tag_length: \t%10.3fK" % (tag_length/1024)) @@ -559,7 +559,7 @@ def read(self, read_offset, read_chunk_size): print("\033[031m\t\t\tDownloading.. OK! %d chunks\033[0m" % iter1) len_remain = (downloaded_stream_len + tag_length - read_offset - read_chunk_size) - # костыль + # Crutch print("#remain: \t%10.3fK" % (len_remain / 1024)) print("diff: \t%10.3fK" % (diff / 1024)) print("#downloaded:\t%10.3fK" % (downloaded_stream_len / 1024)) From e3bdcd037bbe2ffc1e02e4045be53f3a0c4fddc8 Mon Sep 17 00:00:00 2001 From: Ben Klopfenstein Date: Mon, 8 Jan 2018 12:21:44 -0700 Subject: [PATCH 2/3] rename the credentials file so it isnt hidden, since its already in a hidden folder --- gmusicfs/gmusicfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gmusicfs/gmusicfs.py b/gmusicfs/gmusicfs.py index 55538c1..b50fbae 100644 --- a/gmusicfs/gmusicfs.py +++ b/gmusicfs/gmusicfs.py @@ -659,7 +659,7 @@ def __init__(self, username=None, password=None, def __login_and_setup(self, username=None, password=None): # If credentials are not specified, get them from $HOME/.gmusicfs if not username or not password: - cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs/.gmusicfs') + cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs/credentials') if not os.path.isfile(cred_path): raise NoCredentialException( 'No username/password was specified. No config file could ' From c8987bcb5219401df4abecf55ab64455f85d1a7a Mon Sep 17 00:00:00 2001 From: fisher122 Date: Fri, 29 Jun 2018 17:16:46 +0300 Subject: [PATCH 3/3] I returned the receipt of available DEVICE_ID. I returned the use of DEVICE_ID from the config file. Added a bunch of useless spaces. Or not me, but pycharm. Pycharm helped me a lot with debugging. Small cosmetic changes in the names of variables. added a lot of debugging garbage. Sorry.. I still do not understand how it works. Perhaps you need to go back to the code at least a couple of times a month, not half a year. wrote a lot of TODO's. Added symlinks, to prevent duplicates. Since I made a big dump from the aritist's then the tracks in the albums lead to other folders .. or maybe I had to simlink albums? But still I'm not sure how best to allocate it, can it be reduced to the grouping of performers by ARTIST_ID? The paths to the main file are taken by the artist's name, which means that /album_artist/album/track.mp3 is a symlink. Has commented that that .. More correctly reception of covers at start, playlists and still that that. broke playlists. broke the full list of tracks (and did I add it earlier?). to practice shamanism with covers. seem to work. I do not understand how to calculate the size of the file on the server and how to get it entirely (file). I can not get the last few kilobytes of the track - the server returns a void, although wget download entirely. that something is broken in the buffering and loading and in general all this should be thrown out entirely and rewritten to async or something else, but I do not know how yet. It is necessary to rewrite the closing of files .. there may be several openings to 1 track, but the closure is one and it destroys the data. it is necessary to move the closing mechanism to the track ... and generally keep the tracks in memory .... at least not to keep them entirely or to throw them to the disc. Since I added line filtering, then obviously there was a bunch of bugs .. well, in general, somewhere I forgot something and also the proximity. bytes ... bytesarray .. and burn it with a blue flame. What a bug healed already do not remember when. Cyrillic? I cleaned it. Add sample config. ~/gmusicfs/gmusicfs May be rename to credi...s/config/password/? --- gmusicfs.txt | 4 + gmusicfs/gmusicfs.py | 1065 +++++++++++++++++++++++++++++++----------- 2 files changed, 796 insertions(+), 273 deletions(-) create mode 100644 gmusicfs.txt diff --git a/gmusicfs.txt b/gmusicfs.txt new file mode 100644 index 0000000..dad95c1 --- /dev/null +++ b/gmusicfs.txt @@ -0,0 +1,4 @@ +[credentials] +username = username@gmail.com +password = fullySecretMyPassword +deviceid = 0xDEADBEAFFFFF diff --git a/gmusicfs/gmusicfs.py b/gmusicfs/gmusicfs.py index b50fbae..31cc5ce 100644 --- a/gmusicfs/gmusicfs.py +++ b/gmusicfs/gmusicfs.py @@ -2,17 +2,18 @@ import argparse import configparser -import inspect import logging +import inspect import os import pprint import re -import sys import tempfile import traceback import urllib +import hashlib import magic + mime = magic.Magic(mime=True) from errno import ENOENT @@ -21,21 +22,28 @@ from eyed3.id3 import Tag, ID3_V2_4 from fuse import FUSE, FuseOSError, Operations, LoggingMixIn -import gmusicapi.exceptions +# import gmusicapi.exceptions from gmusicapi import Mobileclient as GoogleMusicAPI -# from gmusicapi import Webclient as GoogleMusicWebAPI +from gmusicapi import Webclient as GoogleMusicWebAPI # need for getting device id's logging.basicConfig(level=logging.DEBUG) log = logging.getLogger('gmusicfs') -pp = pprint.PrettyPrinter(indent=2) # For debug logging +pp = pprint.PrettyPrinter(indent=2) # For debug logging + + ALBUM_REGEX = '(?P[^/]+) \((?P[0-9]{4})\)' +ALBUM_REGEX2 = '(?P[^/]+) \((?P[0-9]{4})\)/' ALBUM_FORMAT = "{0.title_printable} ({0.year:04d})" # TODO: check: -TRACK_REGEX = '(?P[0-9]+)-(?P(?P[0-9]+)) - (?P.+?) - ((?P.*)\.mp3)' -TRACK_FORMAT = "{0.disk:02d}-{0.number:02d} - {0.artist_printable} - {0.title_printable}.mp3" +TRACK_REGEX = '(?P<disk>[0-9]+)-(?P<track>(?P<number>[0-9]+)) - (?P<trartist>.+?) - (?P<tralbum>.+?) - ((?P<title>.*)\.mp3)' +TRACK_FORMAT = "{disk:02d}-{number:02d} - {artist_printable} - {album_printable} - {title_printable}.mp3" +TRACK_TRACKS_FORMAT = "{disk:02d}-{number:02d} - {trartist} - {title}.mp3" + +PLAYLIST_REGEX = '^/playlists/(?P<playlist>[^/]+)/' + TRACK_REGEX + '$' +TRACKS_REGEX = '^/tracks/' + TRACK_REGEX + '$' ID3V1_TRAILER_SIZE = 128 @@ -43,31 +51,71 @@ NO_ARTIST_TITLE = "No Artist" -def format_to_path(string_from): +def strip_text (string_from): """Format a name to make it suitable to use as a filename""" return re.sub('[^\w0-9_\.!?#@$ ]+', '_', string_from.strip()) +def getDeviceId (verbose=False): + print("GO") + cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs/gmusicfs') # TODO: move to library? + if not os.path.isfile(cred_path): + raise NoCredentialException( + 'No username/password was specified. No config file could ' + 'be found either. Try creating %s and specifying your ' + 'username/password there. Make sure to chmod 600.' + % cred_path) + if not oct(os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'): + raise NoCredentialException( + 'Config file is not protected. Please run: ' + 'chmod 600 %s' % cred_path) + config = configparser.ConfigParser() + config.read(cred_path) + username = config.get('credentials', 'username') + password = config.get('credentials', 'password') + if not username or not password: + raise NoCredentialException( + 'No username/password could be read from config file' + ': %s' % cred_path) + + api = GoogleMusicWebAPI(debug_logging=verbose) + log.info('Logging in...') + api.login(username, password) + log.info('Login successful.') + + for device in api.get_registered_devices(): + if not 'name' in device or not device['name']: + device['name'] = 'NoName' + if device['id'][1] == 'x': + pp.pprint(device) + + + del api # not forget + + + class NoCredentialException(Exception): pass class Artist(object): - def __init__(self, library, data, name = None): + + def __init__ (self, library, data, name=None): self.__library = library - self.__albums = {} - self.__data = data + self.__albums = {} + self.__tracks = {} + self.__data = data # name - if name is None and 'artist' in data: + if name is None and 'artist' in data and data['artist'].strip() != "": self.__name = data['artist'] - elif name is not None: + elif name is not None and name.strip() != "" and False: # TODO: remove me. bypass self.__name = name else: self.__name = NO_ARTIST_TITLE # name_printable - self.__name_printable = format_to_path(self.__name) + self.__name_printable = strip_text(self.__name) # artist_id if 'artistId' in data: @@ -75,8 +123,6 @@ def __init__(self, library, data, name = None): else: self.__id = self.__name - - @property def id(self): return self.__id @@ -86,32 +132,41 @@ def name(self): return self.__name @property - def name_printable(self): + def name_printable (self): return self.__name_printable @property - def albums(self): + def albums (self): return self.__albums - def add_album(self, album): + @property + def tracks (self): + return self.__tracks + + def add_album (self, album): if album.title_printable not in self.__albums: self.__albums[album.title_printable] = album + def add_track (self, track): + if str(track) not in self.__tracks: + self.__tracks[str(track)] = track + else: + print("TRACK EXISTS") + # TODO: check it - def __str__(self): + def __str__ (self): return "{0.name_printable}".format(self) class Album(object): - def __init__(self, library, data): - self.__library = library - self.__data = data - self.__tracks = {} - self.__artists = {} - self.__art = bytes() - self.__art_mime = "FTW MIME TYPE?" + self.__library = library + self.__data = data + self.__tracks = {} + self.__artists = {} + self.__art = bytes() + self.__art_mime = b'image/jpeg' self.__album_info = None # album_title @@ -121,7 +176,7 @@ def __init__(self, library, data): self.__album_title = NO_ALBUM_TITLE # album_title_printable - self.__album_title_printable = format_to_path(self.__album_title) + self.__album_title_printable = strip_text(self.__album_title) # album_artist ## self.__artist = self.__library.artists.get(data['artistId'][0], None) if 'albumArtist' in data and data['albumArtist'].strip() != "": @@ -132,13 +187,13 @@ def __init__(self, library, data): self.__album_artist = NO_ARTIST_TITLE # album_artist_printable - self.__album_artist_printable = format_to_path(self.__album_artist) + self.__album_artist_printable = strip_text(self.__album_artist) # album_id if 'albumId' in data: self.__id = data['albumId'] - elif format_to_path(self.__album_title) == format_to_path(NO_ALBUM_TITLE): - self.__id = self.__album_title + self.__album_artist #TODO: check it + elif strip_text(self.__album_title) == strip_text(NO_ALBUM_TITLE): + self.__id = self.__album_title + self.__album_artist # TODO: check it else: self.__id = self.__album_title @@ -148,16 +203,20 @@ def __init__(self, library, data): else: self.__year = 0 + # url album art if 'albumArtRef' in data: self.__art_url = data['albumArtRef'][0]['url'] else: self.__art_url = None + # artist if 'artist' in data: self.__artist = data['artist'] else: self.__artist = self.__album_artist + # artist id + # TODO: merge names by id if 'artistId' in data: self.__artistId = data['artistId'] else: @@ -168,66 +227,72 @@ def id(self): return self.__id @property - def tracks(self): + def tracks (self): return self.__tracks @property - def title(self): + def title (self): return self.__album_title @property - def title_printable(self): + def title_printable (self): return self.__album_title_printable @property - def year(self): + def year (self): if not self.__year: - self.__get_year() + self.__get_year() # TODO: needed?? return self.__year @property - def album_artist(self): + def album_artist (self): return self.__album_artist @property - def album_artist_printable(self): + def album_artist_printable (self): return self.__album_artist_printable @property - def art(self): + def art (self): if not self.__art: self.__load_art() return self.__art @property - def art_mime(self): + def art_mime (self): return self.__art_mime - def add_track(self, track): + + + def add_track (self, track): self.__tracks[str(track)] = track if track.id not in self.__library.tracks: # TODO: may be from playlists check it self.__library.tracks[track.id] = track - def add_artist(self, artist): + def add_artist (self, artist): self.__artists[artist.name_printable] = artist - def __get_year(self): + def __get_year (self): # some tracks are not loaded from album_info, let's use them to get the album date release for track in self.__tracks.values(): self.__year = track.year or self.__year # TODO: need check image type! some tracks has png cover - def __load_art(self): + def __load_art (self): if not self.__art_url: return log.info("loading art album: {0.title}".format(self)) self.__art = bytes() - art_path = os.path.join(os.path.expanduser('~'), '.gmusicfs', 'album_arts', self.id + ".jpg") + localArt = "" + art_path = os.path.join(os.path.expanduser('~'), '.gmusicfs', 'album_arts', #TODO: make one var of path's + strip_text(self.id)) # without extension - try: - localArt = open(art_path, "rb") - except: - localArt = "" + for ext in ['.jpg', '.png']: + try: + localArt = open(art_path + ext, "rb") + break + except: + localArt = "" if localArt: print("# ART FROM FS") @@ -239,23 +304,33 @@ def __load_art(self): else: print("# ART FROM URL") u = urllib.request.urlopen(self.__art_url) - # TODO: wtf? + + # TODO: do-while data = u.read() while data: self.__art += data data = u.read() u.close() - print("# ART WRITE TO DISK") - localArt = open(art_path, "wb") + self.__art_mime = mime.from_buffer(self.__art) + + if self.__art_mime == b'image/jpeg': + file_extension = '.jpg' + elif self.__art_mime == b'image/png': + file_extension = '.png' + else: # todo: more variant? + self.__art_mime = None + self.__art = None + return + + print("# ART WRITING TO DISK") + localArt = open(art_path + file_extension, "wb") localArt.write(self.__art) localArt.close() - self.__art_mime = mime.from_buffer(self.__art) - print("loading art album: {0.title_printable} {0.art_mime}".format(self) + " done!") - def __str__(self): + def __str__ (self): title2 = ALBUM_FORMAT.format(self) return title2 @@ -263,65 +338,102 @@ def __str__(self): class Track(object): @property - def id(self): + def id (self): return self.__id @property - def title(self): + def title (self): return self.__title.strip() @property - def title_printable(self): + def stream_size (self): + if not self.__stream_size and True: + if 'length' in self.__data: + self.__stream_size = int(self.__data['length']) + print("#USING length") + elif 'bytes' in self.__data: + self.__stream_size = int(self.__data['bytes']) + print("#USING BYTES") + elif 'estimatedSize' in self.__data: + self.__stream_size = int(self.__data['estimatedSize']) + print("#USING estimatedSize") + elif 'tagSize' in self.__data: + self.__stream_size = int(self.__data['tagSize']) # wtf?? #TODO: need fixing + print("#USING tagSize") + elif 'durationMillis' in self.__data: + self.__stream_size = \ + int( + ( + int(self.__data['durationMillis']) / 1000 + 1 + ) * 320 / 8 * 1000 + ) + print("#USING durationMillis") + else: + print("#USING fake size") + self.__stream_size = 0 + self.__stream_size = 1 * 1024 * 1024 + + return self.__stream_size + self.__tag_length + + @property + def title_printable (self): return self.__title_printable @property - def number(self): + def number (self): return self.__number @property - def disk(self): - return self.__disc + def disk (self): + return self.__disk @property - def album(self): + def album (self): return self.__album @property - def album_printable(self): + def album_printable (self): return self.__album_printable @property - def artist(self): + def artist (self): return self.__artist @property - def artist_printable(self): + def artist_printable (self): return self.__artist_printable @property - def album_artist(self): + def album_artist (self): return self.__album_artist @property - def album_artist_printable(self): + def album_artist_printable (self): return self.__album_artist_printable @property - def year(self): + def year (self): return self.__year - def __init__(self, library, data): - self.__library = library - self.__data = data + @property + def path (self): + return self.__path - self.__artists = {} - self.__albums = {} - self.__stream_url = None + def __init__ (self, library, data): + self.__library = library + self.__data = data + + self.__artists = {} + self.__albums = {} + self.__stream_url = None self.__stream_cache = bytes() self.__rendered_tag = bytes() - self.__tag = "" + self.__tag = "" + self.__stream_size = 0 + self.__tag_length = 0 + self.__path = None - # TODO: I need to figure out where this is used + # TODO: its necessary to understand how it works # track_id if 'track' in data: self.__id = data['trackId'] @@ -344,7 +456,7 @@ def __init__(self, library, data): print("# track has no title") # track_title_printable - self.__title_printable = format_to_path(self.__title) + self.__title_printable = strip_text(self.__title) # track_number if 'trackNumber' in data: @@ -353,14 +465,14 @@ def __init__(self, library, data): self.__number = 0 print("# track has no track num") - #track_disk - if 'discNumber' in data: - self.__disc = data['discNumber'] + # track_disk + if 'diskNumber' in data: + self.__disk = data['diskNumber'] else: - self.__disc = 0 - print("# track has no discNumber") + self.__disk = 0 + print("# track has no diskNumber") - #track_artist + # track_artist if 'artist' in data and data['artist'].strip() != "": self.__artist = data['artist'] elif 'albumArtist' in data and data['albumArtist'].strip() != "": @@ -371,7 +483,7 @@ def __init__(self, library, data): print("# track has no artist") # track_artist_printable - self.__artist_printable = format_to_path(self.__artist) + self.__artist_printable = strip_text(self.__artist) # track_album if 'album' in data and data['album'].strip() != "": @@ -381,7 +493,7 @@ def __init__(self, library, data): print("# track has no album") # track_artist_printable - self.__album_printable = format_to_path(self.__album) + self.__album_printable = strip_text(self.__album) # album_artist if 'albumArtist' in data and data['albumArtist'].strip() != "": @@ -391,8 +503,7 @@ def __init__(self, library, data): print("# track has no albumArtist") # track_artist_printable - self.__album_artist_printable = format_to_path(self.__album_artist) - + self.__album_artist_printable = strip_text(self.__album_artist) if 'year' in data: self.__year = data['year'] @@ -400,19 +511,25 @@ def __init__(self, library, data): self.__year = 0 print("# track has no year") - def add_album(self, album): # TODO: check before insert? may be move outside check in class???? + def add_album (self, album): # TODO: check before insert? may be move outside check in class???? may be name conflicts self.__albums[album.title_printable] = album - def add_artist(self, artist): # TODO: check before insert? may be move outside check in class???? + def add_artist (self, artist): # TODO: check before insert? may be move outside check in class???? may be name conflicts self.__artists[artist.name_printable] = artist - def __gen_tag(self): + def add_path (self, path): + if not self.__path: + self.__path = path + else: + print("exist\n") + + def __gen_tag (self): print("Creating tag idv3...") - self.__tag = Tag() - self.__tag.album = self.album - self.__tag.artist = self.artist - self.__tag.title = self.title - self.__tag.disc_num = int(self.disk) + self.__tag = Tag() + self.__tag.album = self.album + self.__tag.artist = self.artist + self.__tag.title = self.title + self.__tag.disk_num = int(self.disk) self.__tag.track_num = int(self.number) if 'genre' in self.__data: @@ -424,9 +541,14 @@ def __gen_tag(self): if int(self.year) != 0: self.__tag.recording_date = self.year - if self.album_printable in self.__albums and self.__albums[self.album_printable].art: - mime_type = self.__albums[self.album_printable].art_mime #TODO: check mimetype + mime_type = self.__albums[self.album_printable].art_mime # TODO: check mimetype + ''' + print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + print(self.__albums[self.album_printable].art) + print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + ''' + # self.__tag.images.set(0x03, self.__albums[self.album_printable].art, 'image/png' ) self.__tag.images.set(0x03, self.__albums[self.album_printable].art, mime_type, u'Front cover') else: @@ -434,29 +556,66 @@ def __gen_tag(self): # ###### pp.pprint(self.__tag.__dict__) - # TODO: niggercode?? + # TODO: wtf?? tmpfd, tmpfile = tempfile.mkstemp() os.close(tmpfd) + self.__tag.save(tmpfile, ID3_V2_4) tmpfd = open(tmpfile, "rb") self.__rendered_tag = tmpfd.read() - pp.pprint(self.__rendered_tag) + # TODO: NEED CHECKING + # pp.pprint(self.__rendered_tag) tmpfd.close() os.unlink(tmpfile) + return + + def get_attr (self, req_path): + + pp.pprint(req_path) + #pp.pprint(self.__dict__) + # st = {'st_mode': (S_IFREG | 0o777), 'st_nlink': 1, 'st_ctime': 0, 'st_mtime': 0, 'st_atime': 0, 'st_blksize': 512, 'st_blocks': (self.__data['playCount']*1954)} + S_IFLNK = 0o120000 # LINK MASK + print("LET'S GO!\n path1:") + pp.pprint(self.path) + print("vs\n reqed_path") + pp.pprint(req_path) + print("\n\n\n") - def get_attr(self): - st = {'st_mode': (S_IFREG | 0o777), 'st_nlink': 1, 'st_ctime': 0, 'st_mtime': 0, 'st_atime': 0} - # todo: need good data - if 'bytes' in self.__data: + + if self.path == req_path: # path from + linkink = 0 + print("\n\n########## paths coincide!\n\n") + else: + print("\n\n########## NOT paths coincide -> readlink!\n\n") + linkink = S_IFLNK + + st = {'st_mode': (linkink|S_IFREG|0o777), 'st_nlink': 1, 'st_ctime': 0, 'st_mtime': 0, 'st_atime': 0, + 'st_blksize': 512, 'st_blocks': 1} + + # todo: need validate data + # print("#$#$#$#$#$") + # pp.pprint(self.__dict__) + # print("#$#$#$#$#$") + + if self.stream_size != 0: + st['st_size'] = self.stream_size + print("#STREAM SIZE: %d" % self.stream_size) + else: + st['st_size'] = 600 * 1024 * 1024 # 50MB + print("ALERT! UNKNOWN SIZE") + + # TODO: caching filesize + ''' + elif 'bytes' in self.__data: st['st_size'] = int(self.__data['bytes']) elif 'estimatedSize' in self.__data: - st['st_size'] = int(self.__data['estimatedSize']) + st['st_size'] = int(self.stream_size) else: if 'tagSize' in self.__data: st['st_size'] = int(self.__data['tagSize']) # wtf?? @@ -464,6 +623,7 @@ def get_attr(self): print("####tagSize... wtf?") st['st_size'] = 0 st['st_mode'] = (S_IFREG | 0o000) + ''' if 'creationTimestamp' in self.__data: st['st_ctime'] = st['st_mtime'] = int(self.__data['creationTimestamp']) / 1000000 @@ -473,67 +633,70 @@ def get_attr(self): return st - def _open(self): + @staticmethod + def _open (): print("#######file openned!") - print(self.__data) + # print(self.__data) pass # self.__stream_url = urllib.request.urlopen(self.__library.get_stream_url(self.id)) # self.__stream_cache += self.__stream_url.read(64*1024) # Some caching - def read(self, read_offset, read_chunk_size): + + # I XZ how it works! + def read (self, read_offset, read_chunk_size): print("RUN READ: offset:" + str(read_offset) + "; size: " + str(read_chunk_size)) - # Crutch - estimated_size_test = 128*1024 # test 128kb def size - if 'estimatedSize' not in self.__data: + if self.stream_size < 10: print("INVALID TRACK") return # TODO: skip invalid tracks if not self.__tag: # Crating tag only when needed WTAF print("#### # # CRATE TAG") self.__gen_tag() - self.__stream_cache += bytearray(self.__rendered_tag) + self.__stream_cache += bytes(self.__rendered_tag) + # TODO: optimize it! tag_length = len(self.__rendered_tag) # need? + self.__tag_length = tag_length if read_offset == 0 and not self.__stream_url: - # print('####### FIRST RUN TRACK') - self.__stream_url = urllib.request.urlopen(self.__library.get_stream_url(self.id)) - self.__stream_cache += self.__stream_url.read(128 * 1024) # 128kb? - - pp.pprint(self.__stream_url.__dict__) + #if not self.__stream_url: + print('####### FIRST RUN TRACK') + self.__stream_url = urllib.request.FancyURLopener({}).open(self.__library.get_stream_url(self.id)) + self.__stream_size = self.__stream_url.length + self.__stream_cache += self.__stream_url.read(128 * 1024) # 128kb? if not self.__stream_url: # error while getting url # TODO:check it - print("###Could't get stream url!") - print(self) + print("### Could't get stream url!") + # print(self) print(self.id) - print("###") - #return None + print("offset " + str(read_offset)) + print("### END Could't get stream url!") + return None + # TODO: need test slow connection + pos = (read_offset + read_chunk_size) # If we read the end of the file (the last 4 * 4k) and the track is not half loaded, # then we send to the hears of the one who reads - if read_offset + read_chunk_size >= int(self.__data['estimatedSize'])-5*4096 \ - and len(self.__stream_cache)-tag_length < (int(self.__data['estimatedSize']) / 2 + if pos >= int(self.stream_size) - 10 * 4096 \ + and len(self.__stream_cache) - tag_length < (int(self.stream_size) / 2 ): print("\033[032moffset: \t%10.3fK\033[0m" % (read_offset / 1024)) - print("estSize: \t%10.3fK" % (int(self.__data['estimatedSize'])/1024)) + print("estSize: \t%10.3fK" % (int(self.stream_size) / 1024)) print("Read Consistently!") return None - # TODO: need test slow connection - pos = (read_offset + read_chunk_size) - # Crutch print("\n############ debugPos") - print("tag_length: \t%10.3fK" % (tag_length/1024)) - print("#pos: \t%10.3fK" % (pos/1024)) + print("tag_length: \t%10.3fK" % (tag_length / 1024)) + print("#pos: \t%10.3fK" % (pos / 1024)) print("offset: \t%10.3fK" % (read_offset / 1024)) print("size: \t%10.3fK" % (read_chunk_size / 1024)) - print("estSize: \t%10.3fK" % (int(self.__data['estimatedSize'])/1024)) + print("estSize: \t%10.3fK" % (int(self.stream_size) / 1024)) downloaded_stream_len = len(self.__stream_cache) - tag_length diff = downloaded_stream_len - tag_length - (read_offset + read_chunk_size) @@ -543,35 +706,109 @@ def read(self, read_offset, read_chunk_size): print("#from cache...") else: iter1 = 0 - while True and self.__stream_url: + try_no = 0 + while self.__stream_url: print("\033[031m\t\t\tDownloading.. %d chunk\033[0m" % iter1) iter1 += 1 # TODO: check endless loop - chunk = self.__stream_url.read(read_chunk_size * 2) # 2 step up + + remain = (self.__stream_size - downloaded_stream_len) + if remain <= 0: + print("Already downloaded") + break; + else: + print("remain..: %d" % remain) + size_of_buffer = (read_chunk_size * 2) + (32 * 1024) + + if size_of_buffer > self.__stream_size - (len(self.__stream_cache) - tag_length): + print("TRIGGERED FAKING BUFER OVERFLOW@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + if (len(self.__stream_cache) - tag_length) < self.__stream_size: + print("last bytes: '%d' > 0 " % (self.__stream_size - len(self.__stream_cache) - tag_length)) + if read_chunk_size > remain: + print("NEED TESTING (read_chunk_size > remain)!!!!") + size_of_buffer = remain + else: + print("downloaded size > stream size! breaking") + print("'%d' < 0" % (self.__stream_size - len(self.__stream_cache) - tag_length)) + break + chunk = self.__stream_url.read(size_of_buffer) self.__stream_cache += chunk # simply offset?#read all now? downloaded_stream_len = len(self.__stream_cache) - tag_length - diff = downloaded_stream_len - tag_length - (read_offset + read_chunk_size) - print("chunk: \t%10.3fK" % (len(chunk) / 1024)) - if diff > 0 or len(chunk) == 0: + print("##downloaded_stream_len: %d" % downloaded_stream_len) + print("##tag_length: %d" % tag_length) + print("##read_offset: %d" % read_offset) + print("##read_chunk_size: %d" % read_chunk_size) + print("##size_of_buffer: %d" % size_of_buffer) + print("##ro+rcs: %d" % (read_offset + read_chunk_size)) + + print("chunk: %10.3fK" % (len(chunk) / 1000)) + + if (downloaded_stream_len) >= (read_offset + read_chunk_size): + print("################# ALL ?") break + if len(chunk) == 0: + print("done?") + print( + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!len of chunk == 0") + print("downloaded length: %d" % downloaded_stream_len) + print("str_size in google %d" % self.__stream_size) + remain = (self.__stream_size - downloaded_stream_len) + print("remain: %d" % remain) + if remain == 0: + print("STREAM DOWNLOADED") + break + else: # TODO: need checking + print("STREAM IS NOT!!!! FULLY DOWNLOADED") + try_no += 1 + print("try: %d" % try_no) + if try_no > 5: + print("break overload") + break + continue + + print("\033[031m\t\t\tDownloading.. OK! %d chunks\033[0m" % iter1) len_remain = (downloaded_stream_len + tag_length - read_offset - read_chunk_size) # Crutch - print("#remain: \t%10.3fK" % (len_remain / 1024)) - print("diff: \t%10.3fK" % (diff / 1024)) - print("#downloaded:\t%10.3fK" % (downloaded_stream_len / 1024)) + print("#remain: \t%10.3fK" % (len_remain / 1000)) + print("diff: \t%10.3fK" % (diff / 1000)) + print("#downloaded: \t%10.3fK" % (downloaded_stream_len / 1000)) + print("#file_size: \t%10.3fK" % (self.stream_size / 1000)) + print("#read_offset:\t%10.3fK" % (read_offset / 1000)) + print("#read_off+ch:\t%10.3fK" % (read_offset / 1000 + read_chunk_size / 1000)) + print("#tag_size: \t%10.3fK" % (self.__tag_length / 1000)) + print("real_str_siz:\t%10.3fK" % (self.__stream_size / 1000)) + + if len(self.__stream_cache) > self.stream_size: # TODO: neeed?? + print( '### WHAT? DOWNLOADED SIZE > CALCULATED? {0:10d} > {1:10d}'.format( + len(self.__stream_cache), + self.stream_size)) + + if read_offset + read_chunk_size > self.stream_size: + print("## GIVE LAST PART OF FILE") + return self.__stream_cache[read_offset:self.stream_size] + else: + print("#### GIVE SOME OF LAST PARTS OF FILE") + return self.__stream_cache[read_offset:read_offset + read_chunk_size] + if read_offset == 0: + print(self.__stream_cache[read_offset:read_offset + 64]) + return self.__stream_cache[read_offset:read_offset + read_chunk_size] # need len tas size???? - if downloaded_stream_len > read_offset: # TODO: neeed?? - return self.__stream_cache[read_offset:read_offset + read_chunk_size] - return self.__stream_cache[read_offset:read_offset + read_chunk_size] # need len tas size???? - def close(self): + + # TODO: transfer the close logic of closing files from the library + def close (self, ): + print("############ closing track!!!!!!!") + # self.__stream_url.close() + # self.__stream_url = None # pass - """ + + return #TODO: remove this line + if self.__stream_url: log.info("#######################################################killing url") self.__stream_cache = bytes() @@ -579,18 +816,27 @@ def close(self): if self.__rendered_tag: self.__stream_cache += bytearray(self.__rendered_tag) + ''' self.__stream_url.close() self.__stream_url = None - """ + ''' + - def __str__(self): - value2 = TRACK_FORMAT.format(self) + def __str__ (self): + value2 = TRACK_FORMAT.format( + disk = int(self.disk), + number = int(self.number), + artist_printable = self.artist_printable, + album_printable = self.album_printable, + title_printable = self.title_printable + ) return value2 + class Playlist(object): """This class manages playlist information""" - def __init__(self, library, data): + def __init__ (self, library, data): self.__library = library self.__id = data['id'] self.__name = data['name'] @@ -610,116 +856,133 @@ def __init__(self, library, data): else: tr = Track(self.__library, track) - # FISH - print('#1debug') - print(tr) - print('#1end') - print(track) - self.__tracks[tr.title] = tr + self.__tracks[tr.title_printable] = tr except: log.exception("error: {}".format(track)) log.info("Playlist: {0.name}, {1} tracks".format(self, len(self.__tracks))) @property - def id(self): + def id (self): return self.__id @property - def name(self): + def name (self): return self.__name @property - def tracks(self): + def tracks (self): return self.__tracks - def __str__(self): + def __str__ (self): return "{0.name}".format(self) class MusicLibrary(object): """This class reads information about your Google Play Music library""" - def __init__(self, username=None, password=None, - true_file_size=False, verbose=0): + def __init__ (self, username=None, password=None, + true_file_size=False, verbose=0): self.verbose = bool(verbose) - self.api = GoogleMusicAPI(debug_logging=self.verbose) + self.api = GoogleMusicAPI(debug_logging=self.verbose) self.__login_and_setup(username, password) - self.__artists = {} + self.__artists = {} self.__artists_by_name = {} - self.__albums = {} - self.__tracks = {} - self.__playlists = {} + self.__albums = {} + self.__tracks = {} + self.__tracks_by_title = {} + self.__playlists = {} + self.__paths = {} self.rescan() - def __login_and_setup(self, username=None, password=None): + def __login_and_setup (self, username=None, password=None): # If credentials are not specified, get them from $HOME/.gmusicfs + cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs/gmusicfs') #TODO: need to discuss if not username or not password: - cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs/credentials') if not os.path.isfile(cred_path): raise NoCredentialException( - 'No username/password was specified. No config file could ' - 'be found either. Try creating %s and specifying your ' - 'username/password there. Make sure to chmod 600.' - % cred_path) + 'No username/password was specified. No config file could ' + 'be found either. Try creating %s and specifying your ' + 'username/password there. Make sure to chmod 600.' + % cred_path) if not oct(os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'): raise NoCredentialException( - 'Config file is not protected. Please run: ' - 'chmod 600 %s' % cred_path) + 'Config file is not protected. Please run: ' + 'chmod 600 %s' % cred_path) self.config = configparser.ConfigParser() self.config.read(cred_path) username = self.config.get('credentials', 'username') password = self.config.get('credentials', 'password') if not username or not password: raise NoCredentialException( + 'No username/password could be read from config file' + ': %s' % cred_path) + + deviceId = self.config.get('credentials', 'deviceId') + if not username or not password: + raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) + if not deviceId: + raise NoCredentialException( + 'No deviceId could be read from config file' + ': %s' % cred_path) + if deviceId.startswith("0x"): + deviceId = deviceId[2:] + + print("Your device id:") + print(deviceId) log.info('Logging in...') - self.api.login(username, password, GoogleMusicAPI.FROM_MAC_ADDRESS) + self.api.login(username, password, deviceId) log.info('Login successful.') @property - def artists(self): + def artists (self): return self.__artists @property - def artists_by_name(self): + def artists_by_name (self): return self.__artists_by_name @property - def albums(self): + def paths (self): + return self.__paths + + @property + def albums (self): return self.__albums @property - def playlists(self): + def playlists (self): return self.__playlists @property - def tracks(self): + def tracks (self): return self.__tracks - def rescan(self): + @property + def tracks_by_title (self): + return self.__tracks_by_title + + # TODO: timer for rescan? + def rescan (self): """Scan the Google Play Music library""" - self.__artists = {} - self.__artists_by_name = {} - self.__albums = {} - self.__tracks = {} - self.__playlists = {} + self.cleanup() self.__populate_library() - def get_stream_url(self, track_id): + # TODO: may be use webclient for download songs? manual creating meta needless! + def get_stream_url (self, track_id): print("URL:") - url = self.api.get_stream_url(track_id) print(url) return url - def __populate_library(self): + def __populate_library (self): log.info('Gathering track information...') tracks = self.api.get_all_songs() errors = 0 @@ -736,35 +999,82 @@ def __populate_library(self): print("# ALERT! TRACK ALREADY EXISTS IN TRACK BASE? DUPES?") nTrack = self.__tracks[nTrack.id] + if str(nTrack) not in self.__tracks_by_title: + self.__tracks_by_title[str(nTrack)] = nTrack + else: + print("# ALERT! TRACK_BY_NAME ALREADY EXISTS IN TRACK BASE? DUPES?") + nTrack = self.__tracks_by_title[str(nTrack)] + # make album nAlbum = Album(self, track) if nAlbum.id not in self.__albums: - self.__albums[nAlbum.id] = nAlbum print("# new album in library: " + str(nAlbum)) + self.__albums[nAlbum.id] = nAlbum else: - nAlbum = self.__albums[nAlbum.id] print("# old album from library: " + str(nAlbum)) + nAlbum = self.__albums[nAlbum.id] - # TEST LOGIC. IF TRACK HAVE VALID ALBUM ARTIST, then ARTIST = albumArtist - if "albumArtist" in track and track["albumArtist"].strip() != "": # TODO: use track? - # make arti - nArtist = Artist(self, track, nTrack.album_artist_printable) - if nTrack.album_artist_printable not in self.__artists_by_name: - self.__artists_by_name[nTrack.album_artist_printable] = nArtist - print("# new artist from albumArtist: " + str(nArtist)) - else: - nArtist = self.__artists_by_name[nTrack.album_artist_printable] - print("# old artist from albumArtist: " + str(nArtist)) + + # make artist + nArtist = Artist(self, track) + print("artist:" + nArtist.name_printable) + + + + # give Artist by name + if nArtist.name_printable in self.__artists_by_name: + print("# artist from artist name printable: " + str(nArtist)) + nArtist = self.__artists_by_name[nArtist.name_printable] + + # Give Artist by album artist name + elif nTrack.album_artist_printable in self.__artists_by_name: + print("# artist from albumArtist: " + str(nArtist)) + nArtist = self.__artists_by_name[nTrack.album_artist_printable] + + # adding new artist else: - # make artist - nArtist = Artist(self, track) - if nArtist.name_printable not in self.__artists_by_name: - self.__artists_by_name[nArtist.name_printable] = nArtist - print("# new artist from artist: " + str(nArtist)) - else: - nArtist = self.__artists_by_name[nArtist.name_printable] - print("# old artist from artist: " + str(nArtist)) + print("# store new artist : " + str(nArtist)) + if nTrack.album_artist_printable.strip() != "": + print("# new artist store from nTrack.album_artist_printable: " + nTrack.album_artist_printable) + self.__artists_by_name[nTrack.album_artist_printable] = nArtist + + if nArtist.name_printable.strip() != "": + + print("# new artist store from nArtist.name_printable: " + nArtist.name_printable) + self.__artists_by_name[nArtist.name_printable] = nArtist + + + #self.__paths[hashlib.sha224(nPath1.encode('ascii', 'ignore')).hexdigest()] = nTrack + #self.__paths[hashlib.sha224(nPath2.encode('ascii', 'ignore')).hexdigest()] = nTrack + # adding some file paths; #TODO: use artists and other class tree! + if nTrack.album_artist_printable.strip() != "": + path = ( + "/artists/" + + nTrack.album_artist_printable + "/" + + str(nAlbum) + "/" + + str(nTrack) + ) + self.__paths[str(path)] = nTrack + nTrack.add_path(path) + print(nTrack.path) + + if nArtist.name_printable.strip() != "": #TODO: strip needless? + path = ( + "/artists/" + + nArtist.name_printable + "/" + + str(nAlbum) + "/" + + str(nTrack) + ) + self.__paths[str(path)] = nTrack + + nTrack.add_path(path) # add main path + print(nTrack.path) + + if nTrack.path == None: + print("WTF!!!!????!!!???? track does not have artist or album_artist!!! or any invalid?") + nTrack.add_path("/dev/null") # add main path + nTrack.add_album(nAlbum) nTrack.add_artist(nArtist) @@ -772,7 +1082,9 @@ def __populate_library(self): nAlbum.add_track(nTrack) nAlbum.add_artist(nArtist) + nArtist.add_album(nAlbum) + nArtist.add_track(nTrack) ''' print("\n\n\nTRACK:") @@ -789,11 +1101,22 @@ def __populate_library(self): errors += 1 raise - # playlists = self.api.get_all_user_playlist_contents() + + + # refresh album arts + # [albumObj.art for albumObj in self.albums.values()] #TODO: uncoment? + + # TODO: do not use path lists + print("###all paths:") + pp.pprint(self.__paths) + + # TODO: uncomment me + #playlists = self.api.get_all_user_playlist_contents() + playlists = "" for pl in playlists: - name = format_to_path(pl['name']) + name = strip_text(pl['name']) if name[len(name) - 1] == ".": name += "_" @@ -808,63 +1131,94 @@ def __populate_library(self): errors += 1 print("Loaded {} tracks, {} albums, {} artists and {} playlists ({} errors).".format(len(self.__tracks), - len(self.__albums), - len(self.__artists_by_name), - len(self.__playlists), - errors)) + len(self.__albums), + len( self.__artists_by_name), + len(self.__playlists), + errors)) - def cleanup(self): + def cleanup (self): + self.__artists = {} + self.__artists_by_name = {} + self.__albums = {} + self.__tracks = {} + self.__tracks_by_title = {} + self.__playlists = {} + self.__paths = {} pass class GMusicFS(LoggingMixIn, Operations): """Google Music Filesystem""" - def __init__(self, path, username=None, password=None, - true_file_size=False, verbose=0, lowercase=True): + def __init__ (self, path, username=None, password=None, true_file_size=False, verbose=0, lowercase=True): + + self.library_path = path + Operations.__init__(self) - artist = '/artists/(?P<artist>[^/]+)' + artist = '/artists/(?P<artist>[^/]+)' #TODO: remove/move me self.artist_dir = re.compile('^{artist}$'.format(artist=artist)) - self.artist_album_dir = re.compile('^{artist}/{album}$'.format( + self.artist_album_dir = re.compile( + '^{artist}/{album}$'.format( artist=artist, album=ALBUM_REGEX ) ) - self.artist_album_track = re.compile('^{artist}/{album}/{track}$'.format( + + self.artist_album_dir2 = re.compile( + '^{artist}/{album}$'.format( + artist=artist, album=ALBUM_REGEX2 + ) + ) + self.artist_album_track = re.compile( + '^{artist}/{album}/{track}$'.format( artist=artist, album=ALBUM_REGEX, track=TRACK_REGEX ) ) self.playlist_dir = re.compile('^/playlists/(?P<playlist>[^/]+)$') - PLAYLIST_REGEX = '^/playlists/(?P<playlist>[^/]+)/' + TRACK_REGEX + '$' self.playlist_track = re.compile(PLAYLIST_REGEX) print(PLAYLIST_REGEX) + # self.playlist_dir = re.compile('^/playlists/$') + self.tracks_track = re.compile(TRACKS_REGEX) + print(TRACKS_REGEX) + self.__opened_tracks = {} # path -> urllib2_obj # Login to Google Play Music and parse the tracks: self.library = MusicLibrary(username, password, true_file_size=true_file_size, verbose=verbose) log.info("Filesystem ready : %s" % path) - def cleanup(self): + def cleanup (self): self.library.cleanup() def getattr(self, path, fh=None): + print("getattr path:") print(path) + print("end\n") + """Get information about a file or directory""" - artist_dir_m = self.artist_dir.match(path) - artist_album_dir_m = self.artist_album_dir.match(path) - artist_album_track_m = self.artist_album_track.match(path) - playlist_dir_m = self.playlist_dir.match(path) - playlist_track_m = self.playlist_track.match(path) + artist_dir_matches = self.artist_dir.match(path) + artist_album_dir_matches = self.artist_album_dir.match(path) + artist_album_dir2_matches = self.artist_album_dir2.match(path) + + print("regex:") + print(self.artist_album_dir) + print(self.artist_album_dir2) + + artist_album_track_matches = self.artist_album_track.match(path) + playlist_dir_matches = self.playlist_dir.match(path) + playlist_track_matches = self.playlist_track.match(path) + tracks_track_matches = self.tracks_track.match(path) + # Default to a directory st = { - 'st_mode': (S_IFDIR | 0o755), - 'st_nlink': 2 + 'st_mode': (S_IFDIR|0o755), + 'st_nlink': 1 } date = 0 # Make the date really old, so that cp -u works correctly. st['st_ctime'] = st['st_mtime'] = st['st_atime'] = date @@ -882,114 +1236,246 @@ def getattr(self, path, fh=None): print("path /playlists/") pass - elif artist_dir_m: - # print("path \"artist_dir_m\"") + + elif path == '/tracks': + print("path /tracks/") pass - elif artist_album_dir_m: + elif artist_dir_matches: + # print("path \"artist_dir_matches\"") + pass - print("path \"artist_album_dir_m\"") - parts = artist_album_dir_m.groupdict() - print("parts artist_album_dir_m:") + #TODO: cleanup + + elif artist_album_dir2_matches: + + print("path \"artist_album_dir2_matches\"") + + parts = artist_album_dir2_matches.groupdict() + + print("parts artist_album_dir2_matches:") + print(parts) + artist = self.library.artists_by_name[parts['artist']] + print("==================") + print(artist) + print("==================") if parts['album'] not in artist.albums: + raise FuseOSError(ENOENT) - return st + #return st album = artist.albums[parts['album']] + st['st_size'] = len(artist.albums) print(album) + print("==================") + print("############### I ADD DIRECTORY SIZE ") - elif artist_album_track_m: + + elif artist_album_dir_matches: + + print("path \"artist_album_dir_matches\"") + + parts = artist_album_dir_matches.groupdict() + print("parts artist_album_dir_matches:") + print(parts) + artist = self.library.artists_by_name[parts['artist']] + print("==================") + print(artist) + print("==================") + + if parts['album'] not in artist.albums: + raise FuseOSError(ENOENT) + #return st + + album = artist.albums[parts['album']] + st['st_size'] = len(artist.albums) + + print(album) + print("==================") + print("############### I ADD DIRECTORY SIZE ") + + elif artist_album_track_matches: print("path \"artist_album_track_m\"") - parts = artist_album_track_m.groupdict() + parts = artist_album_track_matches.groupdict() print("parts artist_album_track_m:") print(parts) artist = self.library.artists_by_name[parts['artist']] album = artist.albums[parts['album']] - # pp.pprint(album.tracks) - # pp.pprint(album) - title2 = parts['disc'] + "-" + parts['number'] + " - " + parts['trartist'] + " - " + parts['title'] + ".mp3" + + print(parts) + title2 = TRACK_FORMAT.format( + disk=int(parts['disk']), + number=int(parts['number']), + artist_printable=parts['trartist'], + album_printable=parts['tralbum'], + title_printable=parts["title"] + ) track = album.tracks[title2] print("t==================") print(album) print(track) print("############### artist_album_track_m I RETURN TRACK ATTR!") - return track.get_attr() + return track.get_attr(path) + + elif tracks_track_matches: + + print("path \"tracks_track_m\"") + parts = tracks_track_matches.groupdict() + # print("parts tracks_track_m:") + # print(parts) + parts['disk'] = int(parts['disk']) + title2 = TRACK_FORMAT.format( + disk=int(parts['disk']), + number=int(parts['number']), + artist_printable=parts['trartist'], + album_printable=parts['tralbum'], + title_printable=parts["title"] + ) + + if title2 not in self.library.tracks_by_title.keys(): + print("##Error: tracks_track_m -> title2 not found") + raise FuseOSError(ENOENT) + #return + track = self.library.tracks_by_title[title2] + print("OK, ADDING TRACK" + title2) + print(track) + print("############### artist_album_track_m I RETURN TRACK ATTR!") + return track.get_attr(path) - elif playlist_dir_m: - print("path \"playlist_dir_m\"") + elif playlist_dir_matches: + + print("path \"playlist_dir_matches\"") pass - elif playlist_track_m: + elif playlist_track_matches: - print("path \"playlist_track_m\"") + print("path \"playlist_track_matches\"") - parts = playlist_track_m.groupdict() - print("parts playlist_track_m:") + parts = playlist_track_matches.groupdict() + print("parts playlist_track_matches:") print(parts) playlist = self.library.playlists[parts['playlist']] # TODO: revert checking # if parts['title'] in playlist.tracks: if parts['title']: - print("############### playlist_track_m I RETURN ATTR!") - track = playlist.tracks[parts['title']] - # print(playlist.tracks) - return track.get_attr() + print("############### playlist_track_matches I RETURN ATTR!") + + pl_track = playlist.tracks[parts['title']] + + print(parts['title']) + print("error in playlist") + + return pl_track.get_attr() else: - print("############### playlist_track_m I RETURN XYU (st)") + print("############### playlist_track_matches I RETURN XYU (st)") print(playlist.tracks) return st else: - print("############### i return FUSE ERROR") - print(path) + print("\n") + print("##Error: Couldn't get attrs") + print('"'+path+'"') + print("\n") print(parts) + print("\n") raise FuseOSError(ENOENT) return st - def open(self, path, fh): - log.info("open: {} ({})".format(path, fh)) - artist_album_track_m = self.artist_album_track.match(path) - playlist_track_m = self.playlist_track.match(path) + + def gettrack (self, path): + + artist_album_track_m = self.artist_album_track.match(path) + playlist_track_m = self.playlist_track.match(path) + tracks_track_m = self.tracks_track.match(path) + + #print("\nself dict :") + #pp.pprint(self.__dict__) + print("\n\n") + print("gettrack path: " + path) + print("artist_album_track_m: " + str(artist_album_track_m)) + print("playlist_track_m: " + str(playlist_track_m)) + print("tracks_track_m: " + str(tracks_track_m)) + + # open track from album if artist_album_track_m: - parts = artist_album_track_m.groupdict() + parts = artist_album_track_m.groupdict() artist = self.library.artists_by_name[parts['artist']] - album = artist.albums[parts['album']] + album = artist.albums[parts['album']] # TODO: TEST - title2 = parts['disc'] + "-" + parts['number'] + " - " + parts['trartist'] + " - " + parts['title'] + ".mp3" + + title2 = TRACK_FORMAT.format( + disk=int(parts['disk']), + number=int(parts['number']), + artist_printable=parts['trartist'], + album_printable=parts['tralbum'], + title_printable=parts["title"] + ) track = album.tracks[title2] + print("founded track in album: " + str(album)) + + # Open track from tracks + elif tracks_track_m: + parts = tracks_track_m.groupdict() + + title2 = TRACK_FORMAT.format( + disk=int(parts['disk']), + number=int(parts['number']), + artist_printable=parts['trartist'], + album_printable=parts['tralbum'], + title_printable=parts["title"] + ) + track = self.library.tracks_by_title[title2] + print("founded track in tracks") + # Open track elif playlist_track_m: parts = playlist_track_m.groupdict() playlist = self.library.playlists[parts['playlist']] track = playlist.tracks[parts['title']] + print("founded track in playlist " + str(playlist)) else: + print("##Error: track not found in any path's!") + return None + + print("return track!\n\n") + return track + + + + + def open (self, path, fh): + log.info("open: {} ({})".format(path, fh)) + + track = self.gettrack(path) + + if track == None: RuntimeError('unexpected opening of path: %r' % path) # TODO: check it key = path + "-" + str(fh) if not fh in self.__opened_tracks: - self.__opened_tracks[key] = [0, track] + self.__opened_tracks[key] = [0, track] # TODO: check this code. track does not using self.__opened_tracks[key][0] += 1 return fh - def release(self, path, fh): + def release (self, path, fh): log.info("release: {} ({})".format(path, fh)) key = path + "-" + str(fh) track = self.__opened_tracks.get(key, None) @@ -998,8 +1484,10 @@ def release(self, path, fh): track[0] -= 1 if not track[0]: track[1].close() + print("##openned tracks: ") + pp.pprint(self.__opened_tracks) - def read(self, path, size, offset, fh): + def read (self, path, size, offset, fh): log.info("read: {} offset: {} size: {} ({})".format(path, offset, size, fh)) key = path + "-" + str(fh) track = self.__opened_tracks.get(key, None) @@ -1008,14 +1496,31 @@ def read(self, path, size, offset, fh): return track[1].read(offset, size) + def readlink (self, path): + print("My new path:" + path) + track = self.gettrack(path) + print("Me founded track: " + str(track)) + print("Track class name: " + track.__class__.__name__) + + + + if track != None: + #TODO: count the number of slashes + return ( "../../.." + track.path) + else: + print("##ERROR: link path" + track.path + " not avaliable") + raise FuseOSError(ENOENT) + #return "/dev/null" + + # TODO: add file sizes... maybe add radio? - def readdir(self, path, fh): - artist_dir_m = self.artist_dir.match(path) + def readdir (self, path, fh): + artist_dir_m = self.artist_dir.match(path) artist_album_dir_m = self.artist_album_dir.match(path) - playlist_dir_m = self.playlist_dir.match(path) + playlist_dir_m = self.playlist_dir.match(path) if path == '/': - return ['.', '..', 'artists', 'playlists'] + return ['.', '..', 'artists', 'playlists', 'tracks'] elif path == '/artists': # TODO: need filter bad characters? listTMP = list(self.library.artists_by_name.keys()) @@ -1028,14 +1533,21 @@ def readdir(self, path, fh): print(playlistTMP) # TODO: remove return playlistTMP - elif path == '/radio': - radioTMP = ['.', '..'] # + list(self.library.radio.keys()) - print(radioTMP) # TODO: remove - return radioTMP + elif path == '/tracks': + tmpList = [( + str(trackObj) + ) for trackObj in self.library.tracks_by_title.values()] + tracksTMP = ['.', '..'] + tmpList + print("tracks::::") + print(tracksTMP) # TODO: remove + return tracksTMP + + + #TODO: cleanup or/and merge get trrack? elif artist_dir_m: print("artist_dir_m") - # Artist directory, lists albums. + # Artist directory -> lists albums. parts = artist_dir_m.groupdict() print("==========================parts") @@ -1063,6 +1575,7 @@ def readdir(self, path, fh): album = artist.albums[parts['album']] return ['.', '..'] + [str(track) for track in album.tracks.values()] + # print elif playlist_dir_m: # Playlists directory, lists tracks. parts = playlist_dir_m.groupdict() @@ -1109,9 +1622,15 @@ def main(): action='store', dest='gid') parser.add_argument('-l', '--lowercase', help='Convert all path elements to lowercase', action='store_true', dest='lowercase') + parser.add_argument('--deviceid', help='Get the device ids bounded to your account', + action='store_true', dest='deviceId') args = parser.parse_args() + if args.deviceId: + getDeviceId() + return + mountpoint = os.path.abspath(args.mountpoint) # Set verbosity: @@ -1137,7 +1656,7 @@ def main(): # quit() try: FUSE(fs, mountpoint, foreground=args.foreground, - ro=False, nothreads=False, allow_other=args.allow_other, allow_root=args.allow_root, uid=args.uid, + ro=True, nothreads=False, allow_other=args.allow_other, allow_root=args.allow_root, uid=args.uid, gid=args.gid) finally: fs.cleanup()