Skip to content

Commit 47c1ac3

Browse files
committed
feat: enhance downloader to support directory structures
- Copy only Python files from zip download for safety - Preserves user config files (config/apps.yaml etc) - Creates directory structure for python files in subdirs - Ignores cache, backup, and development files This ensures only code gets updated, never user settings.
1 parent 66dd7f3 commit 47c1ac3

File tree

1 file changed

+151
-10
lines changed

1 file changed

+151
-10
lines changed

apps/predbat/download.py

Lines changed: 151 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,36 @@
1010

1111
import os
1212
import requests
13+
import urllib.request
14+
import shutil
15+
import tempfile
1316

1417

1518
def download_predbat_file_from_github(tag, filename, new_filename):
1619
"""
1720
Downloads a predbat source file from github and returns the contents
21+
Now supports files in subdirectories.
1822
1923
Args:
2024
tag (str): The tag to download from (e.g. v1.0.0)
21-
filename (str): The filename to download (e.g. predbat.py)
25+
filename (str): The filename to download (e.g. predbat.py or utils/battery_manager.py)
2226
new_filename (str): The new filename to save the file as
2327
Returns:
2428
str: The contents of the file
2529
"""
26-
url = "https://raw.githubusercontent.com/springfall2008/batpred/" + tag + "/apps/predbat/{}".format(filename)
30+
# Handle both flat files and files in directories
31+
url_path = filename.replace(os.sep, "/") # Ensure forward slashes for URL
32+
url = "https://raw.githubusercontent.com/springfall2008/batpred/" + tag + "/apps/predbat/{}".format(url_path)
2733
print("Downloading {}".format(url))
2834
r = requests.get(url, headers={})
2935
if r.ok:
3036
data = r.text
3137
print("Got data, writing to {}".format(new_filename))
3238
if new_filename:
39+
# Create directory if needed
40+
dir_path = os.path.dirname(new_filename)
41+
if dir_path and not os.path.exists(dir_path):
42+
os.makedirs(dir_path)
3343
with open(new_filename, "w") as han:
3444
han.write(data)
3545
return data
@@ -38,19 +48,66 @@ def download_predbat_file_from_github(tag, filename, new_filename):
3848
return None
3949

4050

41-
def predbat_update_move(version, files):
51+
def predbat_update_move(version, backup_path_or_files):
4252
"""
43-
Move the updated files into place
53+
Move the updated files into place.
54+
Handles both zip-based (backup_path) and individual file approaches.
4455
"""
4556
tag_split = version.split(" ")
4657
if tag_split:
4758
tag = tag_split[0]
4859
this_path = os.path.dirname(__file__)
49-
cmd = ""
50-
for file in files:
51-
cmd += "mv -f {} {} && ".format(os.path.join(this_path, file + "." + tag), os.path.join(this_path, file))
52-
cmd += "echo 'Update complete'"
53-
os.system(cmd)
60+
61+
# Check if we have a backup path (zip method) or file list (individual method)
62+
if isinstance(backup_path_or_files, str) and os.path.isdir(backup_path_or_files):
63+
# Zip method - copy from backup directory
64+
backup_path = backup_path_or_files
65+
print("Moving files from backup directory: {}".format(backup_path))
66+
67+
# Copy all files from backup to current directory
68+
for root, dirs, files in os.walk(backup_path):
69+
for file in files:
70+
source = os.path.join(root, file)
71+
# Calculate relative path from backup_path
72+
rel_path = os.path.relpath(source, backup_path)
73+
dest = os.path.join(this_path, rel_path)
74+
75+
# Create destination directory if needed
76+
dest_dir = os.path.dirname(dest)
77+
if dest_dir and dest_dir != this_path and not os.path.exists(dest_dir):
78+
os.makedirs(dest_dir)
79+
80+
# Copy the file
81+
shutil.copy2(source, dest)
82+
print("Copied {} to {}".format(rel_path, dest))
83+
84+
# Clean up backup directory
85+
shutil.rmtree(backup_path)
86+
print("Cleaned up backup directory")
87+
88+
else:
89+
# Individual file method (backward compatibility)
90+
files = backup_path_or_files
91+
print("Moving individual files with version suffix")
92+
93+
# Process files, creating directories as needed
94+
for file in files:
95+
source = os.path.join(this_path, file + "." + tag)
96+
dest = os.path.join(this_path, file)
97+
98+
# Create destination directory if needed
99+
dest_dir = os.path.dirname(dest)
100+
if dest_dir and dest_dir != this_path and not os.path.exists(dest_dir):
101+
os.makedirs(dest_dir)
102+
103+
# Move the file
104+
if os.path.exists(source):
105+
os.rename(source, dest)
106+
print("Moved {} to {}".format(source, dest))
107+
else:
108+
print("Warning: Source file {} not found".format(source))
109+
110+
print("Update complete")
54111
return True
55112
return False
56113

@@ -72,6 +129,7 @@ def get_files_from_predbat(predbat_code):
72129
def check_install():
73130
"""
74131
Check if Predbat is installed correctly
132+
Now supports files in subdirectories.
75133
"""
76134
this_path = os.path.dirname(__file__)
77135
predbat_file = os.path.join(this_path, "predbat.py")
@@ -91,10 +149,93 @@ def check_install():
91149
return False
92150

93151

152+
def predbat_update_download_zip(version):
153+
"""
154+
Download the defined version of Predbat from Github using zip method (like addon).
155+
This supports directory structures automatically.
156+
"""
157+
this_path = os.path.dirname(__file__)
158+
tag_split = version.split(" ")
159+
if tag_split:
160+
tag = tag_split[0]
161+
162+
print("Downloading Predbat {} using zip method...".format(version))
163+
164+
# Download entire repository as zip
165+
download_url = "https://github.com/springfall2008/batpred/archive/refs/tags/{}.zip".format(tag)
166+
167+
with tempfile.TemporaryDirectory() as temp_dir:
168+
zip_path = os.path.join(temp_dir, "predbat_{}.zip".format(tag))
169+
170+
try:
171+
print("Downloading {}".format(download_url))
172+
urllib.request.urlretrieve(download_url, zip_path)
173+
print("Predbat downloaded successfully")
174+
except Exception as e:
175+
print("Error: Unable to download Predbat - {}".format(e))
176+
return None
177+
178+
print("Extracting Predbat...")
179+
extract_path = os.path.join(temp_dir, "extract")
180+
os.makedirs(extract_path)
181+
shutil.unpack_archive(zip_path, extract_path)
182+
183+
# Find the extracted directory (batpred-X.Y.Z format)
184+
repo_path = os.path.join(extract_path, "batpred-{}".format(tag.replace("v", "")))
185+
predbat_source = os.path.join(repo_path, "apps", "predbat")
186+
187+
if not os.path.exists(predbat_source):
188+
print("Error: Could not find predbat source at {}".format(predbat_source))
189+
return None
190+
191+
# Copy only Python files selectively (safe approach)
192+
backup_path = os.path.join(this_path, "backup_{}".format(tag))
193+
if os.path.exists(backup_path):
194+
shutil.rmtree(backup_path)
195+
os.makedirs(backup_path)
196+
197+
print("Copying Python files to {}...".format(backup_path))
198+
199+
# Copy main *.py files from root directory
200+
for item in os.listdir(predbat_source):
201+
source_path = os.path.join(predbat_source, item)
202+
dest_path = os.path.join(backup_path, item)
203+
204+
if os.path.isfile(source_path) and item.endswith(".py"):
205+
# Copy only Python files
206+
shutil.copy2(source_path, dest_path)
207+
print(" Copied file: {}".format(item))
208+
elif os.path.isdir(source_path) and item not in ["config", "__pycache__", ".ruff_cache", ".git"]:
209+
# Copy subdirectories but only *.py files within them
210+
os.makedirs(dest_path)
211+
print(" Created directory: {}".format(item))
212+
for subitem in os.listdir(source_path):
213+
if subitem.endswith(".py"):
214+
sub_source = os.path.join(source_path, subitem)
215+
sub_dest = os.path.join(dest_path, subitem)
216+
shutil.copy2(sub_source, sub_dest)
217+
print(" Copied: {}/{}".format(item, subitem))
218+
219+
# Note: Only *.py files copied, no config or other file types
220+
221+
print("Download and extraction completed successfully")
222+
return backup_path
223+
224+
return None
225+
226+
94227
def predbat_update_download(version):
95228
"""
96-
Download the defined version of Predbat from Github
229+
Download the defined version of Predbat from Github.
230+
Uses zip method for better directory support.
97231
"""
232+
# Use zip method (more reliable with directories)
233+
backup_path = predbat_update_download_zip(version)
234+
if backup_path:
235+
return backup_path
236+
237+
# Fallback to original method for backward compatibility
238+
print("Zip method failed, trying individual file download...")
98239
this_path = os.path.dirname(__file__)
99240
tag_split = version.split(" ")
100241
if tag_split:

0 commit comments

Comments
 (0)