|
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
|
|
|
|