feat: Add Tab 5 for Stash Task & Tool Control (Start Scan, Identify Scenes, Clean, Generate) with live Job Queue Monitor
This commit is contained in:
@@ -789,9 +789,11 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.handle_performer_image()
|
||||
elif self.path.startswith('/api/image/proxy'):
|
||||
self.handle_image_proxy()
|
||||
# Stash Status & Performer Review APIs
|
||||
# Stash Status, Tasks & Performer Review APIs
|
||||
elif self.path.startswith('/api/stash/status'):
|
||||
self.handle_stash_status()
|
||||
elif self.path.startswith('/api/stash/tasks/jobs'):
|
||||
self.handle_stash_tasks_jobs()
|
||||
elif self.path.startswith('/api/stash/performers/missing-images'):
|
||||
self.handle_stash_missing_images()
|
||||
# SMB API
|
||||
@@ -836,13 +838,23 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.handle_history_undo()
|
||||
elif self.path == '/api/history/clear':
|
||||
self.handle_history_clear()
|
||||
# Stash Lookup, Scan & Image Review APIs
|
||||
# Stash Lookup, Tasks, Scan & Image Review APIs
|
||||
elif self.path == '/api/stash/lookup':
|
||||
self.handle_stash_lookup()
|
||||
elif self.path == '/api/stash/batch-lookup':
|
||||
self.handle_stash_batch_lookup()
|
||||
elif self.path == '/api/stash/scan':
|
||||
self.handle_stash_metadata_scan()
|
||||
elif self.path == '/api/stash/tasks/scan':
|
||||
self.handle_stash_task_scan()
|
||||
elif self.path == '/api/stash/tasks/identify':
|
||||
self.handle_stash_task_identify()
|
||||
elif self.path == '/api/stash/tasks/clean':
|
||||
self.handle_stash_task_clean()
|
||||
elif self.path == '/api/stash/tasks/generate':
|
||||
self.handle_stash_task_generate()
|
||||
elif self.path == '/api/stash/tasks/stop':
|
||||
self.handle_stash_task_stop()
|
||||
elif self.path == '/api/stash/performers/find-images':
|
||||
self.handle_stash_find_images()
|
||||
elif self.path == '/api/stash/performers/commit-image':
|
||||
@@ -1895,7 +1907,222 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
|
||||
# ==================== Stash Library Metadata Scan Trigger ====================
|
||||
# ==================== Stash Task & Tool Control Handlers ====================
|
||||
def handle_stash_tasks_jobs(self):
|
||||
parsed = urllib.parse.urlparse(self.path)
|
||||
params = urllib.parse.parse_qs(parsed.query)
|
||||
stash_url = params.get('stash_url', [DEFAULT_STASH_URL])[0]
|
||||
|
||||
query = '''
|
||||
query JobQueue {
|
||||
jobQueue {
|
||||
id
|
||||
status
|
||||
subTasks
|
||||
description
|
||||
progress
|
||||
startTime
|
||||
endTime
|
||||
addTime
|
||||
error
|
||||
}
|
||||
}
|
||||
'''
|
||||
try:
|
||||
res = execute_stash_graphql(query, stash_url, timeout=10)
|
||||
jobs = res.get('data', {}).get('jobQueue') or []
|
||||
self.send_json_response(200, {"jobs": jobs, "count": len(jobs)})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e), "jobs": []})
|
||||
|
||||
def handle_stash_task_scan(self):
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
post_data = self.rfile.read(content_length)
|
||||
try:
|
||||
payload = json.loads(post_data.decode('utf-8'))
|
||||
stash_url = payload.get('stash_url') or DEFAULT_STASH_URL
|
||||
|
||||
paths = payload.get('paths', [])
|
||||
rescan = payload.get('rescan', False)
|
||||
scanGenerateCovers = payload.get('scanGenerateCovers', True)
|
||||
scanGeneratePreviews = payload.get('scanGeneratePreviews', False)
|
||||
scanGenerateSprites = payload.get('scanGenerateSprites', False)
|
||||
scanGeneratePhashes = payload.get('scanGeneratePhashes', False)
|
||||
|
||||
mutation = '''
|
||||
mutation MetadataScan($input: ScanMetadataInput!) {
|
||||
metadataScan(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
"paths": paths if paths else None,
|
||||
"rescan": bool(rescan),
|
||||
"scanGenerateCovers": bool(scanGenerateCovers),
|
||||
"scanGeneratePreviews": bool(scanGeneratePreviews),
|
||||
"scanGenerateSprites": bool(scanGenerateSprites),
|
||||
"scanGeneratePhashes": bool(scanGeneratePhashes)
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
self.send_json_response(200, {"success": True, "result": res.get('data', {}).get('metadataScan')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
|
||||
def handle_stash_task_identify(self):
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
post_data = self.rfile.read(content_length)
|
||||
try:
|
||||
payload = json.loads(post_data.decode('utf-8'))
|
||||
stash_url = payload.get('stash_url') or DEFAULT_STASH_URL
|
||||
paths = payload.get('paths', [])
|
||||
mode = payload.get('mode', 'autotag')
|
||||
|
||||
if mode == 'autotag':
|
||||
mutation = '''
|
||||
mutation MetadataAutoTag($input: AutoTagMetadataInput!) {
|
||||
metadataAutoTag(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
"paths": paths if paths else None,
|
||||
"performers": bool(payload.get('performers', True)),
|
||||
"studios": bool(payload.get('studios', True)),
|
||||
"tags": bool(payload.get('tags', True))
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
self.send_json_response(200, {"success": True, "job_id": res.get('data', {}).get('metadataAutoTag')})
|
||||
else:
|
||||
mutation = '''
|
||||
mutation MetadataIdentify($input: IdentifyMetadataInput!) {
|
||||
metadataIdentify(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
"paths": paths if paths else None,
|
||||
"options": {
|
||||
"setCoverImage": True,
|
||||
"setOrganized": True
|
||||
}
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
self.send_json_response(200, {"success": True, "job_id": res.get('data', {}).get('metadataIdentify')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
|
||||
def handle_stash_task_clean(self):
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
post_data = self.rfile.read(content_length)
|
||||
try:
|
||||
payload = json.loads(post_data.decode('utf-8'))
|
||||
stash_url = payload.get('stash_url') or DEFAULT_STASH_URL
|
||||
clean_type = payload.get('clean_type', 'metadata')
|
||||
dry_run = bool(payload.get('dry_run', False))
|
||||
paths = payload.get('paths', [])
|
||||
|
||||
results = {}
|
||||
|
||||
if clean_type in ['metadata', 'both']:
|
||||
mutation = '''
|
||||
mutation MetadataClean($input: CleanMetadataInput!) {
|
||||
metadataClean(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
"dryRun": dry_run,
|
||||
"paths": paths if paths else None
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
results['metadataClean'] = res.get('data', {}).get('metadataClean')
|
||||
|
||||
if clean_type in ['generated', 'both']:
|
||||
mutation_gen = '''
|
||||
mutation MetadataCleanGenerated($input: CleanGeneratedInput!) {
|
||||
metadataCleanGenerated(input: $input)
|
||||
}
|
||||
'''
|
||||
variables_gen = {
|
||||
"input": {
|
||||
"dryRun": dry_run,
|
||||
"blobFiles": True,
|
||||
"sprites": True,
|
||||
"screenshots": True,
|
||||
"transcodes": True,
|
||||
"markers": True,
|
||||
"imageThumbnails": True
|
||||
}
|
||||
}
|
||||
res_gen = execute_stash_graphql(mutation_gen, stash_url, variables=variables_gen, timeout=15)
|
||||
results['metadataCleanGenerated'] = res_gen.get('data', {}).get('metadataCleanGenerated')
|
||||
|
||||
self.send_json_response(200, {"success": True, "results": results})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
|
||||
def handle_stash_task_generate(self):
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
post_data = self.rfile.read(content_length)
|
||||
try:
|
||||
payload = json.loads(post_data.decode('utf-8'))
|
||||
stash_url = payload.get('stash_url') or DEFAULT_STASH_URL
|
||||
|
||||
mutation = '''
|
||||
mutation MetadataGenerate($input: GenerateMetadataInput!) {
|
||||
metadataGenerate(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
"covers": bool(payload.get('covers', True)),
|
||||
"sprites": bool(payload.get('sprites', True)),
|
||||
"previews": bool(payload.get('previews', True)),
|
||||
"imagePreviews": bool(payload.get('imagePreviews', True)),
|
||||
"markers": bool(payload.get('markers', True)),
|
||||
"transcodes": bool(payload.get('transcodes', False)),
|
||||
"phashes": bool(payload.get('phashes', True)),
|
||||
"overwrite": bool(payload.get('overwrite', False)),
|
||||
"paths": payload.get('paths') if payload.get('paths') else None
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
self.send_json_response(200, {"success": True, "job_id": res.get('data', {}).get('metadataGenerate')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
|
||||
def handle_stash_task_stop(self):
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
post_data = self.rfile.read(content_length)
|
||||
try:
|
||||
payload = json.loads(post_data.decode('utf-8'))
|
||||
stash_url = payload.get('stash_url') or DEFAULT_STASH_URL
|
||||
job_id = payload.get('job_id')
|
||||
stop_all = payload.get('stop_all', False)
|
||||
|
||||
if stop_all or not job_id:
|
||||
mutation = '''
|
||||
mutation StopAllJobs {
|
||||
stopAllJobs
|
||||
}
|
||||
'''
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=10)
|
||||
self.send_json_response(200, {"success": True, "result": res.get('data', {}).get('stopAllJobs')})
|
||||
else:
|
||||
mutation = '''
|
||||
mutation StopJob($job_id: ID!) {
|
||||
stopJob(job_id: $job_id)
|
||||
}
|
||||
'''
|
||||
res = execute_stash_graphql(mutation, stash_url, variables={"job_id": str(job_id)}, timeout=10)
|
||||
self.send_json_response(200, {"success": True, "result": res.get('data', {}).get('stopJob')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
|
||||
def handle_stash_metadata_scan(self):
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
post_data = self.rfile.read(content_length)
|
||||
|
||||
Reference in New Issue
Block a user