1
|
|
|
"""Module for getting Rainmeter-specific paths.""" |
2
|
|
|
|
3
|
|
|
import os |
4
|
|
|
import re |
5
|
|
|
import filecmp |
6
|
|
|
import ctypes |
7
|
|
|
|
8
|
|
|
# own dependencies |
9
|
|
|
from . import logger |
10
|
|
|
|
11
|
|
|
from .path.program_path_provider import get_cached_program_path |
12
|
|
|
from .path.setting_path_provider import get_cached_setting_path |
13
|
|
|
from .path.program_drive_provider import get_cached_program_drive |
14
|
|
|
from .path.plugin_path_provider import get_cached_plugin_path |
15
|
|
|
from .path.addon_path_provider import get_cached_addon_path |
16
|
|
|
from .path.skin_path_provider import get_cached_skin_path |
17
|
|
|
|
18
|
|
|
|
19
|
|
|
def get_current_path(filepath): |
20
|
|
|
"""Get the value of the #CURRENTPATH# variable for the specified path. |
21
|
|
|
|
22
|
|
|
Returns None if the file path is not in the skins folder |
23
|
|
|
""" |
24
|
|
|
filepath = os.path.normpath(filepath) |
25
|
|
|
|
26
|
|
|
skinspath = get_cached_skin_path() |
27
|
|
|
if not skinspath or not filepath.startswith(skinspath): |
28
|
|
|
logger.info("current path could not be found because" + |
29
|
|
|
" either the skins path could not be found or the current file" + |
30
|
|
|
" is not located in the skins path.") |
31
|
|
|
return |
32
|
|
|
|
33
|
|
|
if os.path.isfile(filepath): |
34
|
|
|
return os.path.dirname(filepath) + "\\" |
35
|
|
|
else: |
36
|
|
|
return filepath + "\\" |
37
|
|
|
|
38
|
|
|
|
39
|
|
|
def get_root_config_path(filepath): |
40
|
|
|
"""Get the value of the #ROOTCONFIGPATH# variable for the specified path. |
41
|
|
|
|
42
|
|
|
Returns None if the path is not in the skins folder |
43
|
|
|
""" |
44
|
|
|
filepath = os.path.normpath(filepath) |
45
|
|
|
|
46
|
|
|
skinspath = get_cached_skin_path() |
47
|
|
|
if not skinspath or not filepath.startswith(skinspath): |
48
|
|
|
logger.info("root config path could not be found" + |
49
|
|
|
" because either the skins path could not be found or the" + |
50
|
|
|
" current file is not located in the skins path.") |
51
|
|
|
return |
52
|
|
|
|
53
|
|
|
relpath = os.path.relpath(filepath, skinspath) |
54
|
|
|
logger.info(os.path.join(skinspath, relpath.split("\\")[0]) + "\\") |
55
|
|
|
|
56
|
|
|
return os.path.join(skinspath, relpath.split("\\")[0]) + "\\" |
57
|
|
|
|
58
|
|
|
|
59
|
|
|
def get_current_file(filepath): |
60
|
|
|
"""Get the value of the #CURRENTFILE# variable for the specified path. |
61
|
|
|
|
62
|
|
|
Returns None if the path is not in the skins folder |
63
|
|
|
""" |
64
|
|
|
filepath = os.path.normpath(filepath) |
65
|
|
|
|
66
|
|
|
skinspath = get_cached_skin_path() |
67
|
|
|
if not skinspath or not filepath.startswith(skinspath): |
68
|
|
|
logger.info("current file could not be found because" + |
69
|
|
|
" either the skins path could not be found or the current" + |
70
|
|
|
" file is not located in the skins path.") |
71
|
|
|
return |
72
|
|
|
|
73
|
|
|
if os.path.isfile(filepath): |
74
|
|
|
return os.path.basename(filepath) |
75
|
|
|
else: |
76
|
|
|
logger.info("specified path is not a file.") |
77
|
|
|
return |
78
|
|
|
|
79
|
|
|
|
80
|
|
|
def get_current_config(filepath): |
81
|
|
|
"""Get the value of the #CURRENTCONFIG# variable for the specified path. |
82
|
|
|
|
83
|
|
|
Returns None if the path is not in the skins folder |
84
|
|
|
""" |
85
|
|
|
filepath = os.path.normpath(filepath) |
86
|
|
|
|
87
|
|
|
skinspath = get_cached_skin_path() |
88
|
|
|
if not skinspath: |
89
|
|
|
logger.info("current config could not be found" + |
90
|
|
|
" because the skins path could not be found.") |
91
|
|
|
return |
92
|
|
|
if not filepath.startswith(skinspath): |
93
|
|
|
logger.info("current config could not be found" + |
94
|
|
|
" because the current file is not located in the skins path.") |
95
|
|
|
|
96
|
|
|
"""workaround for symlinks: |
97
|
|
|
It could exists a symlink in the rainmeter skins folder. |
98
|
|
|
Rainmeter detects it correctly but uses the name of the symlink |
99
|
|
|
for the config name.""" |
100
|
|
|
|
101
|
|
|
logger.info("trying symlink detection to find the skin config.") |
102
|
|
|
skins = os.listdir(skinspath) |
103
|
|
|
|
104
|
|
|
for skin in skins: |
105
|
|
|
check = os.path.join(skinspath, skin) |
106
|
|
|
# 0x0400 is the attribute for reparse point https://msdn.microsoft.com/en-us/library/ee332330(VS.85).aspx |
107
|
|
|
symbolic = os.path.isdir(check) and (ctypes.windll.kernel32.GetFileAttributesW(str(check)) & 0x0400) > 0 |
108
|
|
|
if symbolic: |
109
|
|
|
logger.info("detected a symbolic link '" + check + "' through reparse pointer in file attributes.") |
110
|
|
|
walked = os.walk(check) |
111
|
|
|
# we walk deeper here because there can also be sub configs hidden in folders like <config/subconfig/config.ini> |
112
|
|
|
# which results the config being <config/subconfig> |
113
|
|
|
# at this point <skin> is probably the <config/ |
114
|
|
|
for root, dirs, files in walked: |
115
|
|
|
for file in files: |
116
|
|
|
probe = os.path.join(root, file) |
117
|
|
|
if filecmp.cmp(probe, filepath): |
118
|
|
|
config = os.path.relpath(root, skinspath) |
119
|
|
|
logger.info("found same file '" + file + "'. Recreate interpreted config '" + config + "'.") |
120
|
|
|
|
121
|
|
|
return config |
122
|
|
|
|
123
|
|
|
# when we found the file, we need to reverse the path to detect the sub configs |
124
|
|
|
# _, probe_path_and_file = os.path.splitdrive(probe) |
125
|
|
|
# _, actual_path_and_file = os.path.splitdrive(filepath) |
126
|
|
|
|
127
|
|
|
# prime the algorithm to remove the files |
128
|
|
|
# probe_path, _ = os.path.split(probe_path_and_file) |
129
|
|
|
# actual_path, _ = os.path.split(actual_path_and_file) |
130
|
|
|
|
131
|
|
|
# while 1: |
132
|
|
|
# probe_path, probe_folder = os.path.split(probe_path) |
133
|
|
|
# actual_path, actual_folder = os.path.split(actual_path) |
134
|
|
|
# if probe_folder != actual_folder: |
135
|
|
|
# logger.info("Found last not matching folder '" + probe_folder + "' with '" + actual_folder + "'") |
136
|
|
|
|
137
|
|
|
# directory = os.path.dirname(probe) |
138
|
|
|
|
139
|
|
|
|
140
|
|
|
|
141
|
|
|
return |
142
|
|
|
|
143
|
|
|
# before you get the whole path called from the skin D:\Documents\Rainmeter\Skins\test\test.ini |
144
|
|
|
if os.path.isfile(filepath): |
145
|
|
|
filepath = os.path.dirname(filepath) |
146
|
|
|
# after the file name is trimmed D:\Documents\Rainmeter\Skins\test |
147
|
|
|
|
148
|
|
|
# the result is the directory path minus the skin path |
149
|
|
|
# D:\Documents\Rainmeter\Skins\test minus D:\Documents\Rainmeter\Skins\test equals test |
150
|
|
|
# thus the config is called <test> |
151
|
|
|
return os.path.relpath(filepath, skinspath) |
152
|
|
|
|
153
|
|
|
|
154
|
|
|
def get_resources_path(filepath): |
155
|
|
|
"""Get the value of the #@# variable for the specified path. |
156
|
|
|
|
157
|
|
|
Returns None if the path is not in the skins folder |
158
|
|
|
""" |
159
|
|
|
rfp = get_root_config_path(filepath) |
160
|
|
|
|
161
|
|
|
if not rfp: |
162
|
|
|
return |
163
|
|
|
logger.info(os.path.join(rfp, "@Resources") + "\\") |
164
|
|
|
return os.path.join(rfp, "@Resources") + "\\" |
165
|
|
|
|
166
|
|
|
|
167
|
|
|
def replace_variables(string, filepath): |
168
|
|
|
"""Replace Rainmeter built-in variables and Windows environment variables in string. |
169
|
|
|
|
170
|
|
|
Replaces occurrences of the following variables in the string: |
171
|
|
|
#CURRENTFILE# |
172
|
|
|
#CURRENTPATH# |
173
|
|
|
#ROOTCONFIGPATH# |
174
|
|
|
#CURRENTCONFIG# |
175
|
|
|
#@# |
176
|
|
|
#SKINSPATH# |
177
|
|
|
#SETTINGSPATH# |
178
|
|
|
#PROGRAMPATH# |
179
|
|
|
#PROGRAMDRIVE# |
180
|
|
|
#ADDONSPATH# |
181
|
|
|
#PLUGINSPATH# |
182
|
|
|
Any Windows environment variables (like %APPDATA%) |
183
|
|
|
filepath must be a skin file located in a subdirectory of the skins folder |
184
|
|
|
""" |
185
|
|
|
variables = { |
186
|
|
|
# lambdas for lazy evaluation |
187
|
|
|
"#CURRENTFILE#": lambda: get_current_file(filepath), |
188
|
|
|
"#CURRENTPATH#": lambda: get_current_path(filepath), |
189
|
|
|
"#ROOTCONFIGPATH#": lambda: get_root_config_path(filepath), |
190
|
|
|
"#CURRENTCONFIG#": lambda: get_current_config(filepath), |
191
|
|
|
"#@#": lambda: get_resources_path(filepath), |
192
|
|
|
"#SKINSPATH#": get_cached_skin_path, |
193
|
|
|
"#SETTINGSPATH#": get_cached_setting_path, |
194
|
|
|
"#PROGRAMPATH#": get_cached_program_path, |
195
|
|
|
"#PROGRAMDRIVE#": get_cached_program_drive, |
196
|
|
|
"#ADDONSPATH#": get_cached_addon_path, |
197
|
|
|
"#PLUGINSPATH#": get_cached_plugin_path |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
pattern = re.compile("(?i)" + "|".join(list(variables.keys()))) |
201
|
|
|
# replace Rainmeter variables |
202
|
|
|
repl = pattern.sub(lambda x: variables[x.group().upper()](), |
203
|
|
|
string) |
204
|
|
|
# expand windows environment variables |
205
|
|
|
repl = os.path.expandvars(repl) |
206
|
|
|
return repl |
207
|
|
|
|
208
|
|
|
|
209
|
|
|
def make_path(string, filepath): |
210
|
|
|
"""Make the string into an absolute path of an existing file or folder. |
211
|
|
|
|
212
|
|
|
Replacing Rainmeter built-in variables relative to the file specified in |
213
|
|
|
filepath (see replace_variables()) will return None if the file or folder |
214
|
|
|
doesn't exist, or if string is None or empty. |
215
|
|
|
""" |
216
|
|
|
if not string: |
217
|
|
|
return None |
218
|
|
|
|
219
|
|
|
repl = replace_variables(string, filepath) |
220
|
|
|
norm = os.path.normpath(repl) |
221
|
|
|
|
222
|
|
|
# For relative paths, try folder of current file first |
223
|
|
|
|
224
|
|
|
if not os.path.isabs(norm): |
225
|
|
|
curpath = get_current_path(filepath) |
226
|
|
|
if curpath: |
227
|
|
|
abso = os.path.join(curpath, norm) |
228
|
|
|
else: |
229
|
|
|
abso = os.path.join(os.path.dirname(filepath), norm) |
230
|
|
|
|
231
|
|
|
if os.path.exists(abso): |
232
|
|
|
return abso |
233
|
|
|
|
234
|
|
|
# if that doesn't work, try relative to skins path |
235
|
|
|
# (for #CURRENTCONFIG#) |
236
|
|
|
abso = os.path.join(get_cached_skin_path(), norm) |
237
|
|
|
if os.path.exists(abso): |
238
|
|
|
return abso |
239
|
|
|
# for absolute paths, try opening containing folder if file does not exist |
240
|
|
|
else: |
241
|
|
|
if os.path.exists(norm): |
242
|
|
|
return norm |
243
|
|
|
|
244
|
|
|
if os.path.exists(os.path.dirname(norm)): |
245
|
|
|
return os.path.dirname(norm) |
246
|
|
|
|
247
|
|
|
return |
248
|
|
|
|