Merge pull request #444 from Nezreka/refactor/lift-redownload-start
Lift redownload_start to core/library/redownload.py
This commit is contained in:
commit
7556e79dbd
2 changed files with 272 additions and 189 deletions
259
core/library/redownload.py
Normal file
259
core/library/redownload.py
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
"""Track redownload endpoint — lifted from web_server.py.
|
||||||
|
|
||||||
|
Body is byte-identical to the original. The ``spotify_client`` proxy
|
||||||
|
+ helper shims for the iTunes/Deezer registry clients let the body
|
||||||
|
resolve its original names; ``_resolve_library_file_path``,
|
||||||
|
``_attempt_download_with_candidates``, and ``missing_download_executor``
|
||||||
|
are injected via init() because they live in web_server.py.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from flask import jsonify, request
|
||||||
|
|
||||||
|
from core.runtime_state import (
|
||||||
|
download_batches,
|
||||||
|
download_tasks,
|
||||||
|
tasks_lock,
|
||||||
|
)
|
||||||
|
from core.metadata.registry import (
|
||||||
|
get_deezer_client,
|
||||||
|
get_itunes_client,
|
||||||
|
get_spotify_client,
|
||||||
|
)
|
||||||
|
from database.music_database import get_database
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_itunes_client():
|
||||||
|
"""Mirror of web_server._get_itunes_client — delegates to registry."""
|
||||||
|
return get_itunes_client()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_deezer_client():
|
||||||
|
"""Mirror of web_server._get_deezer_client — delegates to registry."""
|
||||||
|
return get_deezer_client()
|
||||||
|
|
||||||
|
|
||||||
|
class _SpotifyClientProxy:
|
||||||
|
"""Resolves the global Spotify client lazily through core.metadata.registry."""
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
client = get_spotify_client()
|
||||||
|
if client is None:
|
||||||
|
raise AttributeError(name)
|
||||||
|
return getattr(client, name)
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return get_spotify_client() is not None
|
||||||
|
|
||||||
|
|
||||||
|
spotify_client = _SpotifyClientProxy()
|
||||||
|
|
||||||
|
|
||||||
|
# Injected at runtime via init().
|
||||||
|
_resolve_library_file_path = None
|
||||||
|
_attempt_download_with_candidates = None
|
||||||
|
missing_download_executor = None
|
||||||
|
|
||||||
|
|
||||||
|
def init(resolve_library_file_path_fn, attempt_download_with_candidates_fn, executor):
|
||||||
|
"""Bind shared helpers from web_server."""
|
||||||
|
global _resolve_library_file_path, _attempt_download_with_candidates
|
||||||
|
global missing_download_executor
|
||||||
|
_resolve_library_file_path = resolve_library_file_path_fn
|
||||||
|
_attempt_download_with_candidates = attempt_download_with_candidates_fn
|
||||||
|
missing_download_executor = executor
|
||||||
|
|
||||||
|
|
||||||
|
def redownload_start(track_id):
|
||||||
|
"""Start downloading a specific track from a selected source to replace the current file."""
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
metadata = data.get('metadata', {})
|
||||||
|
candidate = data.get('candidate', {})
|
||||||
|
delete_old = data.get('delete_old_file', True)
|
||||||
|
|
||||||
|
if not candidate.get('username') or not candidate.get('filename'):
|
||||||
|
return jsonify({"success": False, "error": "candidate with username and filename required"}), 400
|
||||||
|
|
||||||
|
# Get current track info for old file path
|
||||||
|
database = get_database()
|
||||||
|
conn = database._get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT file_path FROM tracks WHERE id = ?", (track_id,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
old_file_path = None
|
||||||
|
if row and row['file_path'] and delete_old:
|
||||||
|
old_file_path = _resolve_library_file_path(row['file_path'])
|
||||||
|
|
||||||
|
task_id = f"redownload_{track_id}_{int(time.time())}"
|
||||||
|
batch_id = f"redownload_batch_{track_id}"
|
||||||
|
|
||||||
|
# Fetch full track details from the metadata source for pipeline parity
|
||||||
|
# This gives us track_number, disc_number, full album data
|
||||||
|
meta_source = metadata.get('_source', '')
|
||||||
|
meta_id = metadata.get('id', '')
|
||||||
|
full_track_details = None
|
||||||
|
full_album_data = None
|
||||||
|
|
||||||
|
if meta_id:
|
||||||
|
try:
|
||||||
|
if meta_source == 'spotify' and spotify_client and spotify_client.is_authenticated():
|
||||||
|
full_track_details = spotify_client.get_track_details(meta_id)
|
||||||
|
if full_track_details and full_track_details.get('album', {}).get('id'):
|
||||||
|
full_album_data = spotify_client.get_album(full_track_details['album']['id'])
|
||||||
|
elif meta_source == 'itunes':
|
||||||
|
_it = _get_itunes_client()
|
||||||
|
results = _it._lookup(id=meta_id, entity='song')
|
||||||
|
if results:
|
||||||
|
for r in results:
|
||||||
|
if r.get('wrapperType') == 'track':
|
||||||
|
full_track_details = r
|
||||||
|
break
|
||||||
|
elif meta_source == 'deezer':
|
||||||
|
_dz = _get_deezer_client()
|
||||||
|
full_track_details = _dz._api_get(f'track/{meta_id}')
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"[Redownload] Could not fetch full track details: {e}")
|
||||||
|
|
||||||
|
# Build track data with full metadata for pipeline parity
|
||||||
|
track_number = None
|
||||||
|
disc_number = 1
|
||||||
|
album_data = {'name': metadata.get('album', '')}
|
||||||
|
|
||||||
|
if full_track_details:
|
||||||
|
if meta_source == 'spotify':
|
||||||
|
track_number = full_track_details.get('track_number')
|
||||||
|
disc_number = full_track_details.get('disc_number', 1)
|
||||||
|
album_raw = full_track_details.get('album', {})
|
||||||
|
if album_raw:
|
||||||
|
album_images = album_raw.get('images', [])
|
||||||
|
album_data = {
|
||||||
|
'id': album_raw.get('id', ''),
|
||||||
|
'name': album_raw.get('name', metadata.get('album', '')),
|
||||||
|
'release_date': album_raw.get('release_date', ''),
|
||||||
|
'album_type': album_raw.get('album_type', 'album'),
|
||||||
|
'total_tracks': album_raw.get('total_tracks', 0),
|
||||||
|
'images': album_images,
|
||||||
|
'image_url': album_images[0]['url'] if album_images else '',
|
||||||
|
}
|
||||||
|
elif meta_source == 'itunes':
|
||||||
|
track_number = full_track_details.get('trackNumber')
|
||||||
|
disc_number = full_track_details.get('discNumber', 1)
|
||||||
|
elif meta_source == 'deezer':
|
||||||
|
track_number = full_track_details.get('track_position')
|
||||||
|
disc_number = full_track_details.get('disk_number', 1)
|
||||||
|
|
||||||
|
track_data = {
|
||||||
|
'id': meta_id,
|
||||||
|
'name': metadata.get('name', ''),
|
||||||
|
'artists': [{'name': metadata.get('artist', '')}],
|
||||||
|
'album': album_data,
|
||||||
|
'duration_ms': metadata.get('duration_ms', 0),
|
||||||
|
'track_number': track_number,
|
||||||
|
'disc_number': disc_number,
|
||||||
|
'_is_explicit_album_download': bool(full_album_data or (album_data.get('id'))),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build explicit context if we have full album data
|
||||||
|
if full_album_data or album_data.get('id'):
|
||||||
|
track_data['_explicit_album_context'] = full_album_data if isinstance(full_album_data, dict) else album_data
|
||||||
|
track_data['_explicit_artist_context'] = {'name': metadata.get('artist', ''), 'id': '', 'genres': []}
|
||||||
|
|
||||||
|
# Create batch
|
||||||
|
with tasks_lock:
|
||||||
|
download_batches[batch_id] = {
|
||||||
|
'queue': [task_id],
|
||||||
|
'queue_index': 1, # Already past the first (only) item
|
||||||
|
'active_count': 1, # One worker is about to start
|
||||||
|
'max_concurrent': 1,
|
||||||
|
'playlist_id': f'redownload_{track_id}',
|
||||||
|
'playlist_name': f"Redownload: {metadata.get('artist', '')} - {metadata.get('name', '')}",
|
||||||
|
'phase': 'downloading',
|
||||||
|
'total_tracks': 1,
|
||||||
|
'completed_count': 0,
|
||||||
|
'failed_count': 0,
|
||||||
|
'cancelled_tracks': set(),
|
||||||
|
'permanently_failed_tracks': [],
|
||||||
|
'force_download': True,
|
||||||
|
'auto_initiated': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
download_tasks[task_id] = {
|
||||||
|
'status': 'queued',
|
||||||
|
'track_info': track_data,
|
||||||
|
'playlist_id': f'redownload_{track_id}',
|
||||||
|
'batch_id': batch_id,
|
||||||
|
'track_index': 0,
|
||||||
|
'download_id': None,
|
||||||
|
'username': None,
|
||||||
|
'filename': None,
|
||||||
|
'retry_count': 0,
|
||||||
|
'cached_candidates': [],
|
||||||
|
'used_sources': set(),
|
||||||
|
'status_change_time': time.time(),
|
||||||
|
'metadata_enhanced': False,
|
||||||
|
'error_message': None,
|
||||||
|
'_redownload_context': {
|
||||||
|
'library_track_id': track_id,
|
||||||
|
'old_file_path': old_file_path,
|
||||||
|
'delete_old_file': delete_old,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build a TrackResult-like candidate and submit to download
|
||||||
|
def _run_redownload():
|
||||||
|
try:
|
||||||
|
from core.soulseek_client import TrackResult
|
||||||
|
from core.itunes_client import Track as MetaTrack
|
||||||
|
tr = TrackResult(
|
||||||
|
username=candidate['username'],
|
||||||
|
filename=candidate['filename'],
|
||||||
|
size=candidate.get('size', 0),
|
||||||
|
bitrate=candidate.get('bitrate', 0),
|
||||||
|
duration=candidate.get('duration', 0),
|
||||||
|
quality=candidate.get('quality', ''),
|
||||||
|
free_upload_slots=candidate.get('free_upload_slots', 0),
|
||||||
|
upload_speed=candidate.get('upload_speed', 0),
|
||||||
|
queue_length=candidate.get('queue_length', 0),
|
||||||
|
)
|
||||||
|
tr.artist = metadata.get('artist', '')
|
||||||
|
tr.title = metadata.get('name', '')
|
||||||
|
tr.album = metadata.get('album', '')
|
||||||
|
tr.confidence = candidate.get('confidence', 1.0)
|
||||||
|
|
||||||
|
# Build a proper Track object (not a dict) — _attempt_download_with_candidates
|
||||||
|
# accesses track.artists, track.album etc. as attributes
|
||||||
|
artist_name = metadata.get('artist', '')
|
||||||
|
track_obj = MetaTrack(
|
||||||
|
id=metadata.get('id', ''),
|
||||||
|
name=metadata.get('name', ''),
|
||||||
|
artists=[artist_name] if artist_name else ['Unknown'],
|
||||||
|
album=metadata.get('album', ''),
|
||||||
|
duration_ms=metadata.get('duration_ms', 0),
|
||||||
|
popularity=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
_attempt_download_with_candidates(task_id, [tr], track_obj, batch_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Redownload failed: {e}", exc_info=True)
|
||||||
|
with tasks_lock:
|
||||||
|
if task_id in download_tasks:
|
||||||
|
download_tasks[task_id]['status'] = 'failed'
|
||||||
|
download_tasks[task_id]['error_message'] = str(e)
|
||||||
|
|
||||||
|
missing_download_executor.submit(_run_redownload)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"success": True,
|
||||||
|
"task_id": task_id,
|
||||||
|
"batch_id": batch_id,
|
||||||
|
"message": "Redownload started",
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error starting redownload: {e}", exc_info=True)
|
||||||
|
return jsonify({"success": False, "error": str(e)}), 500
|
||||||
202
web_server.py
202
web_server.py
|
|
@ -11777,197 +11777,15 @@ def redownload_search_sources(track_id):
|
||||||
return jsonify({"success": False, "error": str(e)}), 500
|
return jsonify({"success": False, "error": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
from core.library.redownload import (
|
||||||
|
redownload_start as _redownload_start_impl,
|
||||||
|
init as _init_redownload,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/library/track/<track_id>/redownload/start', methods=['POST'])
|
@app.route('/api/library/track/<track_id>/redownload/start', methods=['POST'])
|
||||||
def redownload_start(track_id):
|
def redownload_start(track_id):
|
||||||
"""Start downloading a specific track from a selected source to replace the current file."""
|
return _redownload_start_impl(track_id)
|
||||||
try:
|
|
||||||
data = request.get_json()
|
|
||||||
metadata = data.get('metadata', {})
|
|
||||||
candidate = data.get('candidate', {})
|
|
||||||
delete_old = data.get('delete_old_file', True)
|
|
||||||
|
|
||||||
if not candidate.get('username') or not candidate.get('filename'):
|
|
||||||
return jsonify({"success": False, "error": "candidate with username and filename required"}), 400
|
|
||||||
|
|
||||||
# Get current track info for old file path
|
|
||||||
database = get_database()
|
|
||||||
conn = database._get_connection()
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("SELECT file_path FROM tracks WHERE id = ?", (track_id,))
|
|
||||||
row = cursor.fetchone()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
old_file_path = None
|
|
||||||
if row and row['file_path'] and delete_old:
|
|
||||||
old_file_path = _resolve_library_file_path(row['file_path'])
|
|
||||||
|
|
||||||
task_id = f"redownload_{track_id}_{int(time.time())}"
|
|
||||||
batch_id = f"redownload_batch_{track_id}"
|
|
||||||
|
|
||||||
# Fetch full track details from the metadata source for pipeline parity
|
|
||||||
# This gives us track_number, disc_number, full album data
|
|
||||||
meta_source = metadata.get('_source', '')
|
|
||||||
meta_id = metadata.get('id', '')
|
|
||||||
full_track_details = None
|
|
||||||
full_album_data = None
|
|
||||||
|
|
||||||
if meta_id:
|
|
||||||
try:
|
|
||||||
if meta_source == 'spotify' and spotify_client and spotify_client.is_authenticated():
|
|
||||||
full_track_details = spotify_client.get_track_details(meta_id)
|
|
||||||
if full_track_details and full_track_details.get('album', {}).get('id'):
|
|
||||||
full_album_data = spotify_client.get_album(full_track_details['album']['id'])
|
|
||||||
elif meta_source == 'itunes':
|
|
||||||
_it = _get_itunes_client()
|
|
||||||
results = _it._lookup(id=meta_id, entity='song')
|
|
||||||
if results:
|
|
||||||
for r in results:
|
|
||||||
if r.get('wrapperType') == 'track':
|
|
||||||
full_track_details = r
|
|
||||||
break
|
|
||||||
elif meta_source == 'deezer':
|
|
||||||
_dz = _get_deezer_client()
|
|
||||||
full_track_details = _dz._api_get(f'track/{meta_id}')
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"[Redownload] Could not fetch full track details: {e}")
|
|
||||||
|
|
||||||
# Build track data with full metadata for pipeline parity
|
|
||||||
track_number = None
|
|
||||||
disc_number = 1
|
|
||||||
album_data = {'name': metadata.get('album', '')}
|
|
||||||
|
|
||||||
if full_track_details:
|
|
||||||
if meta_source == 'spotify':
|
|
||||||
track_number = full_track_details.get('track_number')
|
|
||||||
disc_number = full_track_details.get('disc_number', 1)
|
|
||||||
album_raw = full_track_details.get('album', {})
|
|
||||||
if album_raw:
|
|
||||||
album_images = album_raw.get('images', [])
|
|
||||||
album_data = {
|
|
||||||
'id': album_raw.get('id', ''),
|
|
||||||
'name': album_raw.get('name', metadata.get('album', '')),
|
|
||||||
'release_date': album_raw.get('release_date', ''),
|
|
||||||
'album_type': album_raw.get('album_type', 'album'),
|
|
||||||
'total_tracks': album_raw.get('total_tracks', 0),
|
|
||||||
'images': album_images,
|
|
||||||
'image_url': album_images[0]['url'] if album_images else '',
|
|
||||||
}
|
|
||||||
elif meta_source == 'itunes':
|
|
||||||
track_number = full_track_details.get('trackNumber')
|
|
||||||
disc_number = full_track_details.get('discNumber', 1)
|
|
||||||
elif meta_source == 'deezer':
|
|
||||||
track_number = full_track_details.get('track_position')
|
|
||||||
disc_number = full_track_details.get('disk_number', 1)
|
|
||||||
|
|
||||||
track_data = {
|
|
||||||
'id': meta_id,
|
|
||||||
'name': metadata.get('name', ''),
|
|
||||||
'artists': [{'name': metadata.get('artist', '')}],
|
|
||||||
'album': album_data,
|
|
||||||
'duration_ms': metadata.get('duration_ms', 0),
|
|
||||||
'track_number': track_number,
|
|
||||||
'disc_number': disc_number,
|
|
||||||
'_is_explicit_album_download': bool(full_album_data or (album_data.get('id'))),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Build explicit context if we have full album data
|
|
||||||
if full_album_data or album_data.get('id'):
|
|
||||||
track_data['_explicit_album_context'] = full_album_data if isinstance(full_album_data, dict) else album_data
|
|
||||||
track_data['_explicit_artist_context'] = {'name': metadata.get('artist', ''), 'id': '', 'genres': []}
|
|
||||||
|
|
||||||
# Create batch
|
|
||||||
with tasks_lock:
|
|
||||||
download_batches[batch_id] = {
|
|
||||||
'queue': [task_id],
|
|
||||||
'queue_index': 1, # Already past the first (only) item
|
|
||||||
'active_count': 1, # One worker is about to start
|
|
||||||
'max_concurrent': 1,
|
|
||||||
'playlist_id': f'redownload_{track_id}',
|
|
||||||
'playlist_name': f"Redownload: {metadata.get('artist', '')} - {metadata.get('name', '')}",
|
|
||||||
'phase': 'downloading',
|
|
||||||
'total_tracks': 1,
|
|
||||||
'completed_count': 0,
|
|
||||||
'failed_count': 0,
|
|
||||||
'cancelled_tracks': set(),
|
|
||||||
'permanently_failed_tracks': [],
|
|
||||||
'force_download': True,
|
|
||||||
'auto_initiated': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
download_tasks[task_id] = {
|
|
||||||
'status': 'queued',
|
|
||||||
'track_info': track_data,
|
|
||||||
'playlist_id': f'redownload_{track_id}',
|
|
||||||
'batch_id': batch_id,
|
|
||||||
'track_index': 0,
|
|
||||||
'download_id': None,
|
|
||||||
'username': None,
|
|
||||||
'filename': None,
|
|
||||||
'retry_count': 0,
|
|
||||||
'cached_candidates': [],
|
|
||||||
'used_sources': set(),
|
|
||||||
'status_change_time': time.time(),
|
|
||||||
'metadata_enhanced': False,
|
|
||||||
'error_message': None,
|
|
||||||
'_redownload_context': {
|
|
||||||
'library_track_id': track_id,
|
|
||||||
'old_file_path': old_file_path,
|
|
||||||
'delete_old_file': delete_old,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Build a TrackResult-like candidate and submit to download
|
|
||||||
def _run_redownload():
|
|
||||||
try:
|
|
||||||
from core.soulseek_client import TrackResult
|
|
||||||
from core.itunes_client import Track as MetaTrack
|
|
||||||
tr = TrackResult(
|
|
||||||
username=candidate['username'],
|
|
||||||
filename=candidate['filename'],
|
|
||||||
size=candidate.get('size', 0),
|
|
||||||
bitrate=candidate.get('bitrate', 0),
|
|
||||||
duration=candidate.get('duration', 0),
|
|
||||||
quality=candidate.get('quality', ''),
|
|
||||||
free_upload_slots=candidate.get('free_upload_slots', 0),
|
|
||||||
upload_speed=candidate.get('upload_speed', 0),
|
|
||||||
queue_length=candidate.get('queue_length', 0),
|
|
||||||
)
|
|
||||||
tr.artist = metadata.get('artist', '')
|
|
||||||
tr.title = metadata.get('name', '')
|
|
||||||
tr.album = metadata.get('album', '')
|
|
||||||
tr.confidence = candidate.get('confidence', 1.0)
|
|
||||||
|
|
||||||
# Build a proper Track object (not a dict) — _attempt_download_with_candidates
|
|
||||||
# accesses track.artists, track.album etc. as attributes
|
|
||||||
artist_name = metadata.get('artist', '')
|
|
||||||
track_obj = MetaTrack(
|
|
||||||
id=metadata.get('id', ''),
|
|
||||||
name=metadata.get('name', ''),
|
|
||||||
artists=[artist_name] if artist_name else ['Unknown'],
|
|
||||||
album=metadata.get('album', ''),
|
|
||||||
duration_ms=metadata.get('duration_ms', 0),
|
|
||||||
popularity=0,
|
|
||||||
)
|
|
||||||
|
|
||||||
_attempt_download_with_candidates(task_id, [tr], track_obj, batch_id)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Redownload failed: {e}", exc_info=True)
|
|
||||||
with tasks_lock:
|
|
||||||
if task_id in download_tasks:
|
|
||||||
download_tasks[task_id]['status'] = 'failed'
|
|
||||||
download_tasks[task_id]['error_message'] = str(e)
|
|
||||||
|
|
||||||
missing_download_executor.submit(_run_redownload)
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"task_id": task_id,
|
|
||||||
"batch_id": batch_id,
|
|
||||||
"message": "Redownload started",
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error starting redownload: {e}", exc_info=True)
|
|
||||||
return jsonify({"success": False, "error": str(e)}), 500
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/library/artist/<artist_id>/sync', methods=['POST'])
|
@app.route('/api/library/artist/<artist_id>/sync', methods=['POST'])
|
||||||
|
|
@ -33155,6 +32973,12 @@ _init_wishlist_failed(
|
||||||
sweep_fn=_sweep_empty_download_directories,
|
sweep_fn=_sweep_empty_download_directories,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_init_redownload(
|
||||||
|
resolve_library_file_path_fn=_resolve_library_file_path,
|
||||||
|
attempt_download_with_candidates_fn=_attempt_download_with_candidates,
|
||||||
|
executor=missing_download_executor,
|
||||||
|
)
|
||||||
|
|
||||||
_init_debug_info(
|
_init_debug_info(
|
||||||
soulsync_version=SOULSYNC_VERSION,
|
soulsync_version=SOULSYNC_VERSION,
|
||||||
direct_run=_DIRECT_RUN,
|
direct_run=_DIRECT_RUN,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue