kapsikkum-unmanic – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3  
4 """
5 unmanic.directoryinfo.py
6  
7 Written by: Josh.5 <jsunnex@gmail.com>
8 Date: 02 Jul 2021, (10:59 AM)
9  
10 Copyright:
11 Copyright (C) Josh Sunnex - All Rights Reserved
12  
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19  
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22  
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
27 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
28 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
29 OR OTHER DEALINGS IN THE SOFTWARE.
30  
31 """
32 import json
33 import os
34 import configparser
35  
36  
37 class UnmanicDirectoryInfoException(Exception):
38 def __init__(self, message, path):
39 errmsg = '%s: file %s' % (message, path)
40 Exception.__init__(self, errmsg)
41 self.message = message
42 self.path = path
43  
44 def __repr__(self):
45 return self.message
46  
47 __str__ = __repr__
48  
49  
50 class UnmanicDirectoryInfo:
51 """
52 UnmanicDirectoryInfo
53  
54 Manages the reading and writing of the '.unmanic' files located in the directories
55 parsed by Unmanic's library scanner or any plugins.
56  
57 Legacy support:
58 On read, if the config is an INI file, uses ConfigParser.get to fetch information
59 and convert it to JSON.
60 INI was initially used in order to be a simple syntax for manually editing.
61 However, INI is not ideal for managing file names and paths with special characters
62  
63 """
64  
65 def __init__(self, directory):
66 self.path = os.path.join(directory, '.unmanic')
67 self.json_data = None
68 self.config_parser = None
69 # If the path does not exist, do not try to read it
70 if not os.path.exists(self.path):
71 self.json_data = {}
72 return
73 # First read JSON data
74 try:
75 with open(self.path) as infile:
76 self.json_data = json.load(infile)
77 # Migrate JSON to latest formatting
78 self.__migrate_json_formatting()
79 except json.decoder.JSONDecodeError:
80 pass
81 # If we were unable to import the JSON data, attempt to read as INI
82 if self.json_data is None:
83 try:
84 self.config_parser = configparser.ConfigParser(allow_no_value=True)
85 self.config_parser.read(self.path)
86 # Migrate file to JSON
87 self.__migrate_to_json()
88 except configparser.MissingSectionHeaderError:
89 pass
90 except configparser.NoSectionError:
91 pass
92 except configparser.NoOptionError:
93 pass
94 # If we still do not have JSON data at this point, something has gone wrong
95 if self.json_data is None:
96 raise UnmanicDirectoryInfoException("Failed to read directory info", self.path)
97  
98 def __migrate_to_json(self):
99 """
100 Migrate data from INI to JSON
101  
102 :return:
103 """
104 sections = self.config_parser.sections()
105 json_data = {}
106 for section in sections:
107 section_data = {}
108 for key in self.config_parser[section]:
109 section_data[key.lower()] = self.config_parser.get(section, key)
110 json_data[section] = section_data
111 self.json_data = json_data
112  
113 def __migrate_json_formatting(self):
114 """
115 Migrate JSON to latest format
116  
117 Migration 1:
118 As Unmanic may be used on platforms that view files as case-insensitive,
119 we should ensure that all keys are also stored this way.
120  
121 :return:
122 """
123 # Ensure all keys are lower case
124 sections = [s for s in self.json_data]
125 for section in sections:
126 # Sections remain case sensitive, but keys must be lowercase
127 keys = [k for k in self.json_data[section]]
128 for key in keys:
129 if key != key.lower():
130 self.json_data[section][key.lower()] = self.json_data[section][key]
131 del self.json_data[section][key]
132  
133 def set(self, section, option, value=None):
134 """
135 Set an option.
136  
137 :param section:
138 :param option:
139 :param value:
140 :return:
141 """
142 # Ensure keys are always lower-case
143 option = option.lower()
144 if self.json_data is not None:
145 if not self.json_data.get(section):
146 self.json_data[section] = {}
147 self.json_data[section][option] = value
148 return
149 elif self.config_parser:
150 if not self.config_parser.has_section(section):
151 self.config_parser.add_section(section)
152 self.config_parser.set(section, option, value)
153 return
154 raise UnmanicDirectoryInfoException("Failed to set section '{}' option '{}' value '{}'".format(section, option, value),
155 self.path)
156  
157 def get(self, section, option):
158 """
159 Get an option
160  
161 :param section:
162 :param option:
163 :return:
164 """
165 option = option.lower()
166 if self.json_data is not None:
167 return self.json_data.get(section, {}).get(option)
168 elif self.config_parser:
169 return self.config_parser.get(section, option)
170 raise UnmanicDirectoryInfoException("Failed to get section '{}' option '{}'".format(section, option), self.path)
171  
172 def save(self):
173 """
174 Saves the data to file.
175  
176 :return:
177 """
178 if self.json_data is not None:
179 with open(self.path, 'w') as outfile:
180 json.dump(self.json_data, outfile, indent=2)
181 return
182 elif self.config_parser:
183 with open(self.path, 'w') as outfile:
184 self.config_parser.write(outfile)
185 return
186 raise UnmanicDirectoryInfoException("Failed to save directory info", self.path)
187  
188  
189 if __name__ == '__main__':
190 directory_info = UnmanicDirectoryInfo('/tmp/unmanic')
191 directory_info.set('test_section', 'key', 'value')
192 directory_info.save()
193 print(directory_info.get('test_section', 'key'))
194 directory_info.set('"section with double quotes"', '"key with double quotes"', '"value with double quotes"')
195 directory_info.save()
196 print(directory_info.get('"section with double quotes"', '"key with double quotes"'))