-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathlibrary.py
127 lines (97 loc) · 4.48 KB
/
library.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import xml.etree.ElementTree as etree
import shutil
import os
import logging
import threading
from logger import get_logger
from datetime import datetime
from conf import *
logger = get_logger(__name__)
class Library:
_instance = None
def __init__(self, path):
Library._instance = self
self.semaphore = threading.Semaphore()
self.traktor_path = path
self.library_path = os.path.join(path, "collection.nml")
if os.path.exists(self.library_path):
self.semaphore.acquire()
self.tree = etree.parse(self.library_path, parser=etree.XMLParser(encoding="utf-8"))
self.collection = self.tree.getroot().find("COLLECTION")
self.playlists = self.tree.getroot().find("PLAYLISTS")
self.semaphore.release()
else:
logger.critical("Traktor library does not exist: {}".format(self.library_path))
@staticmethod
def instance():
if Library._instance:
Library._instance.semaphore.acquire()
Library._instance.semaphore.release()
return Library._instance
def flush(self, path=None):
"""
Write collection XML file to the disk performing a backup of the existing collection first
:return:
"""
backup_path = None
logger.debug("Flushing Traktor library")
if path is None:
backup_path = self._backup()
path = self.library_path
self.tree.write(path, encoding="utf-8", xml_declaration=True)
return backup_path
def create_new(self):
version = self.tree.getroot().attrib["VERSION"]
root = etree.Element("NML", attrib={"VERSION": version})
etree.SubElement(root, "MUSICFOLDERS")
etree.SubElement(root, "COLLECTION")
etree.SubElement(root, "PLAYLISTS")
return etree.ElementTree(root)
@staticmethod
def create_playlist_structure(tree, name, total):
playlists = tree.getroot().find("PLAYLISTS")
playlists.clear()
parent = etree.SubElement(playlists, "NODE", attrib={"TYPE": "FOLDER", "NAME": "$ROOT"})
parent = etree.SubElement(parent, "SUBNODES", attrib={"COUNT": "1"})
parent = etree.SubElement(parent, "NODE", attrib={"TYPE": "PLAYLIST", "NAME": name})
return etree.SubElement(parent, "PLAYLIST", attrib={"ENTRIES": str(total), "TYPE": "LIST", "UUID": ""})
def _backup(self):
logger.debug("Creating a backup of Traktor library")
backup_path = os.path.join(self.traktor_path, "Backup", "Librarian")
if not os.path.exists(backup_path):
os.makedirs(backup_path)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
destination = os.path.join(backup_path, "collection_{}.nml".format(timestamp))
shutil.copy(self.library_path, destination)
return backup_path
def get_full_path(self, entry, include_volume=False, traktorize=False):
"""
Return the full path to a file from the XML entry. If include_volume flag is true, then the path is prepended
with the volume name (drive letter on Windows), as this information is required in playlist entries.
:param entry: XML entry to extract a full path from. If traktorize flag is set, then the path is converted to
the Traktor format with a colon after each path part
:param include_volume: if True, then the volume name (disk drive) is appended before the path
:param traktorize if True, then converts path to the Traktor format
:return: absolute path to a file
"""
location = entry.find("LOCATION")
dir = location.get("DIR").replace(":", "")
file = location.get("FILE")
full_path = os.path.join(dir, file)
if traktorize:
full_path = self.traktorize_path(full_path)
if include_volume:
volume = location.get("VOLUME")
full_path = volume + full_path # we cannot use os.path.join as the path is in the absolute form already
return full_path
def traktorize_path(self, path):
"""
Convert a path to the Traktor format, that is with a colon preceding each directory name
:param path: Path to convert
:return: Traktorized path
"""
# / is a valid filename character on OSX, so we must escape it before splitting the path
path = path.replace("//", "%___%")
path_parts = path.split("/")
separator = "/:"
return separator.join(path_parts).replace("%___%", "//")