Revisão | ebe378b053c5cebf9505f4656abc8988eadc90a5 (tree) |
---|---|
Hora | 2017-10-15 00:30:14 |
Autor | Jaime Marquínez Ferrándiz <jaime.marquinez.ferrandiz@fast...> |
Commiter | Jaime Marquínez Ferrándiz |
Initial commit
@@ -0,0 +1,2 @@ | ||
1 | +syntax: glob | |
2 | +*.pyc |
@@ -0,0 +1,24 @@ | ||
1 | +This is free and unencumbered software released into the public domain. | |
2 | + | |
3 | +Anyone is free to copy, modify, publish, use, compile, sell, or | |
4 | +distribute this software, either in source code form or as a compiled | |
5 | +binary, for any purpose, commercial or non-commercial, and by any | |
6 | +means. | |
7 | + | |
8 | +In jurisdictions that recognize copyright laws, the author or authors | |
9 | +of this software dedicate any and all copyright interest in the | |
10 | +software to the public domain. We make this dedication for the benefit | |
11 | +of the public at large and to the detriment of our heirs and | |
12 | +successors. We intend this dedication to be an overt act of | |
13 | +relinquishment in perpetuity of all present and future rights to this | |
14 | +software under copyright law. | |
15 | + | |
16 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
17 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
18 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
19 | +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
20 | +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
21 | +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
22 | +OTHER DEALINGS IN THE SOFTWARE. | |
23 | + | |
24 | +For more information, please refer to <http://unlicense.org/> |
@@ -0,0 +1,2 @@ | ||
1 | +from pkgutil import extend_path | |
2 | +__path__ = extend_path(__path__, __name__) |
@@ -0,0 +1,145 @@ | ||
1 | +from __future__ import unicode_literals | |
2 | + | |
3 | +import os.path | |
4 | +import os | |
5 | +import sqlite3 | |
6 | +import shutil | |
7 | +import datetime | |
8 | + | |
9 | +from beets.plugins import BeetsPlugin | |
10 | +from beets.ui import Subcommand | |
11 | +from beets.util import prune_dirs, bytestring_path | |
12 | +from beets.dbcore import OrQuery | |
13 | +from beets.library import parse_query_parts, Item | |
14 | + | |
15 | +_CREATE_DATABASE_SCRIPT=''' | |
16 | +CREATE TABLE song(idSong INTEGER PRIMARY KEY, filename TEXT NOT NULL, date TIMESTAMP, synced BOOL); | |
17 | +CREATE TABLE query(idQuery INTEGER PRIMARY KEY AUTOINCREMENT, query TEXT NOT NULL); | |
18 | +CREATE TABLE querysong(idQuery INT REFERENCES query, idSong INT REFERENCES song, | |
19 | + PRIMARY KEY (idQuery, idSong) | |
20 | + ); | |
21 | +CREATE TABLE playlist(idPlaylist INTEGER PRIMARY KEY AUTOINCREMENT, playlist TEXT NOT NULL); | |
22 | +CREATE TABLE playlistsong(idPlaylist INTEGER REFERENCES playlist, idSong INTEGER REFERENCES song, listIndex INTEGER NOT NULL, | |
23 | + PRIMARY KEY (idPlaylist, idSong, listIndex)); | |
24 | +''' | |
25 | + | |
26 | +class PlayerSync(BeetsPlugin): | |
27 | + def info(self, *args, **kwargs): | |
28 | + return self._log.info(*args, **kwargs) | |
29 | + | |
30 | + def error(self, *args, **kwargs): | |
31 | + return self._log.error(*args, **kwargs) | |
32 | + | |
33 | + def commands(self): | |
34 | + sync_command = Subcommand('sync', help='Sync music player') | |
35 | + sync_command.func = self.sync | |
36 | + return [sync_command] | |
37 | + | |
38 | + def sync(self, lib, opts, args): | |
39 | + for device in self.config['devices']: | |
40 | + self.sync_device(device, lib, opts, args) | |
41 | + self.info('Done') | |
42 | + | |
43 | + def sync_device(self, config, lib, opts, args): | |
44 | + self.info("Syncing {0}", config['path']) | |
45 | + path = os.path.expanduser(config['path'].get()) | |
46 | + if not os.path.exists(path): | |
47 | + self.error('Path doesn\'t exists {0}', path) | |
48 | + return | |
49 | + self.info('Creating database') | |
50 | + db_file = os.path.join(path, 'beetssync.db') | |
51 | + db = sqlite3.connect(db_file) | |
52 | + db.text_factory = str | |
53 | + if not db.execute('SELECT * FROM sqlite_master WHERE name="song"').fetchall(): | |
54 | + with db: | |
55 | + db.executescript(_CREATE_DATABASE_SCRIPT) | |
56 | + with db: | |
57 | + db.execute('DELETE FROM query') | |
58 | + db.execute('DELETE FROM querysong') | |
59 | + db.execute('DELETE FROM playlist') | |
60 | + db.execute('DELETE FROM playlistsong') | |
61 | + | |
62 | + for query in config['queries'].get(list): | |
63 | + self.info('Processing query: "{0}"', query) | |
64 | + items = lib.items(query) | |
65 | + for item in items: | |
66 | + self.add_item(lib, db, item) | |
67 | + with db: | |
68 | + db.execute('INSERT INTO query (query) VALUES (?)', (query,)) | |
69 | + query_id = db.execute('SELECT idQuery FROM query WHERE query=?', (query,)).fetchone()[0] | |
70 | + db.executemany('INSERT INTO querysong VALUES (?, ?)', [(query_id, item['id']) for item in items]) | |
71 | + | |
72 | + for playlist in config['playlists'].get(list): | |
73 | + self.info('Processing playlist: "{0}"', playlist) | |
74 | + playlist_file = os.path.expanduser(playlist) | |
75 | + with open(playlist_file, 'rt') as f: | |
76 | + paths = [line for line in (line.strip() for line in f) if line and not line.startswith('#')] | |
77 | + items = [] | |
78 | + for song_path in paths: | |
79 | + query, q_sort = parse_query_parts(['path:' + song_path], Item) | |
80 | + item = list(lib.items(OrQuery(query)))[0] | |
81 | + self.add_item(lib, db, item) | |
82 | + items.append(item) | |
83 | + with db: | |
84 | + db.execute( | |
85 | + 'INSERT INTO playlist (playlist) VALUES (?)', | |
86 | + (playlist,)) | |
87 | + playlist_id = db.execute( | |
88 | + 'SELECT idPlaylist FROM playlist WHERE playlist=?', | |
89 | + (playlist,)).fetchone()[0] | |
90 | + db.executemany( | |
91 | + 'INSERT INTO playlistsong VALUES (?, ?, ?)', | |
92 | + [(playlist_id, item['id'], index) for index, item in enumerate(items)]) | |
93 | + | |
94 | + self.info('Syncing songs') | |
95 | + for item_id, song_path in db.execute(''' | |
96 | + SELECT song.idSong, song.filename FROM song JOIN querysong ON song.idSong=querysong.idSong WHERE synced=0 or synced is NULL | |
97 | + UNION | |
98 | + SELECT song.idSong, song.filename FROM song JOIN playlistsong ON song.idSong=playlistsong.idSong WHERE synced=0 or synced is NULL | |
99 | + '''): | |
100 | + item = lib.get_item(item_id) | |
101 | + copy_path = os.path.join(path, song_path) | |
102 | + if not os.path.exists(item['path']): | |
103 | + self.info('"{0}" does not exist', item['path']) | |
104 | + continue | |
105 | + with db: | |
106 | + self.info('Copying "{0}"', item) | |
107 | + if not os.path.exists(os.path.dirname(copy_path)): | |
108 | + os.makedirs(os.path.dirname(copy_path)) | |
109 | + shutil.copyfile(item['path'], copy_path) | |
110 | + db.execute('UPDATE song SET synced=1,date=? WHERE idSong=?', (datetime.datetime.now(), item_id)) | |
111 | + | |
112 | + for item_id, song_path in db.execute(''' | |
113 | + SELECT song.idSong, filename FROM song | |
114 | + LEFT JOIN querysong ON song.idSong=querysong.idSong | |
115 | + LEFT JOIN playlistsong ON song.idSong=playlistsong.idSong | |
116 | + WHERE synced=1 and (querysong.idQuery is NULL and playlistsong.idPlaylist is NULL) | |
117 | + '''): | |
118 | + item = lib.get_item(item_id) | |
119 | + copy_path = os.path.join(path, song_path) | |
120 | + with db: | |
121 | + self.info('Removing "{0}"', item) | |
122 | + if os.path.exists(copy_path): | |
123 | + os.unlink(copy_path) | |
124 | + prune_dirs(os.path.dirname(copy_path), root=path) | |
125 | + db.execute('DELETE FROM song WHERE idSong=?', (item_id,)) | |
126 | + | |
127 | + self.info('Creating playlists') | |
128 | + for playlist_id, playlist_path in db.execute('SELECT * FROM playlist'): | |
129 | + with open(os.path.join(path, os.path.basename(playlist_path)), 'wt') as f: | |
130 | + for (filename,) in db.execute(''' | |
131 | + SELECT filename FROM song JOIN playlistsong ON song.idSong=playlistsong.idSong | |
132 | + WHERE idPlaylist=? | |
133 | + ORDER BY listIndex | |
134 | + ''', (playlist_id,)): | |
135 | + f.write(filename + '\n') | |
136 | + | |
137 | + self.info('Finished syncing {0}', config['path']) | |
138 | + | |
139 | + def add_item(self, lib, db, item): | |
140 | + item_id = item['id'] | |
141 | + fullpath = item['path'] | |
142 | + copy_path = os.path.relpath(fullpath, lib.directory) | |
143 | + if not db.execute('SELECT * FROM song WHERE idSong=?', (item_id,)).fetchall(): | |
144 | + with db: | |
145 | + db.execute('INSERT INTO song (idSong, filename) VALUES (?,?)', (item_id, copy_path)) |