mirror of
https://github.com/zoffline/zwift-offline.git
synced 2026-06-12 11:01:32 -07:00
50bfa24d99
Fix user home sync status in single player mode. Sync workouts before rendering user home. Changes in launcher pages. Revert unnecessary changes.
96 lines
3.0 KiB
Python
96 lines
3.0 KiB
Python
import hashlib
|
|
import os
|
|
import xml.etree.ElementTree as ET
|
|
|
|
|
|
MANIFEST_FILENAME = 'workouts.files'
|
|
|
|
|
|
def manifest_file(workouts_dir):
|
|
return os.path.join(workouts_dir, MANIFEST_FILENAME)
|
|
|
|
|
|
def parse_int(value):
|
|
try:
|
|
return int((value or '0').strip() or '0')
|
|
except ValueError:
|
|
return 0
|
|
|
|
|
|
def file_checksum(content):
|
|
return (-sum(content)) % 256
|
|
|
|
|
|
def file_guid(filename):
|
|
digest = hashlib.sha1(filename.encode('utf-8')).digest()
|
|
return (int.from_bytes(digest[:4], 'big') & 0x7fffffff) or 1
|
|
|
|
|
|
def load_manifest(workouts_dir):
|
|
path = manifest_file(workouts_dir)
|
|
if not os.path.exists(path):
|
|
return []
|
|
try:
|
|
root = ET.parse(path).getroot()
|
|
except ET.ParseError:
|
|
return []
|
|
entries = []
|
|
for node in root.findall('custom_file'):
|
|
name = (node.findtext('name') or '').strip()
|
|
if not name:
|
|
continue
|
|
entries.append({
|
|
'name': name,
|
|
'time': parse_int(node.findtext('time')),
|
|
'guid': parse_int(node.findtext('guid')),
|
|
'checksum': parse_int(node.findtext('checksum')),
|
|
'deleted': (node.findtext('deleted') or 'false').strip().lower() == 'true',
|
|
})
|
|
return entries
|
|
|
|
|
|
def save_manifest(workouts_dir, entries):
|
|
os.makedirs(workouts_dir, exist_ok=True)
|
|
root = ET.Element('custom_file_directory')
|
|
for entry in sorted(entries, key=lambda item: item['name'].lower()):
|
|
node = ET.SubElement(root, 'custom_file')
|
|
ET.SubElement(node, 'name').text = entry['name']
|
|
ET.SubElement(node, 'time').text = str(int(entry['time']))
|
|
ET.SubElement(node, 'guid').text = str(int(entry['guid']))
|
|
ET.SubElement(node, 'checksum').text = str(int(entry['checksum']))
|
|
ET.SubElement(node, 'deleted').text = 'true' if entry.get('deleted') else 'false'
|
|
ET.SubElement(root, 'deleted_files')
|
|
tree = ET.ElementTree(root)
|
|
ET.indent(tree, space=' ')
|
|
path = manifest_file(workouts_dir)
|
|
tree.write(path, encoding='utf-8', xml_declaration=False)
|
|
with open(path, 'a', encoding='utf-8') as fd:
|
|
fd.write('\n')
|
|
return path
|
|
|
|
|
|
def upsert_manifest_entry(workouts_dir, filename, content, timestamp=None):
|
|
entries = [entry for entry in load_manifest(workouts_dir) if entry['name'] != filename]
|
|
timestamp = int(timestamp if timestamp is not None else os.path.getmtime(os.path.join(workouts_dir, filename)))
|
|
entry = {
|
|
'name': filename,
|
|
'time': timestamp,
|
|
'guid': file_guid(filename),
|
|
'checksum': file_checksum(content),
|
|
'deleted': False,
|
|
}
|
|
entries.append(entry)
|
|
save_manifest(workouts_dir, entries)
|
|
return entry
|
|
|
|
|
|
def remove_prefixed_workouts(workouts_dir, prefixes):
|
|
entries = load_manifest(workouts_dir)
|
|
removed = 0
|
|
for entry in entries:
|
|
if entry['name'].startswith(tuple(prefixes)):
|
|
entry['deleted'] = True
|
|
removed += 1
|
|
save_manifest(workouts_dir, entries)
|
|
return removed
|