231 lines
9.6 KiB
Python
231 lines
9.6 KiB
Python
import clr
|
|
clr.AddReference("System.Windows.Forms")
|
|
clr.AddReference("System")
|
|
clr.AddReference("System.Web.Extensions")
|
|
from System.Windows.Forms import MessageBox
|
|
from System.Diagnostics import Process, ProcessStartInfo
|
|
from System.Web.Script.Serialization import JavaScriptSerializer
|
|
from System.IO import Path, File
|
|
from System.Text import UTF8Encoding
|
|
|
|
serializer = JavaScriptSerializer()
|
|
serializer.MaxJsonLength = 2147483647 # Max int32 - handle large book selections
|
|
SCRIPT_DIR = Path.GetDirectoryName(__file__)
|
|
PYTHON_EXE = r"pythonw.exe"
|
|
|
|
# ==============================================================================
|
|
# @Name Sync: Export to CBZ
|
|
# @Hook Books
|
|
# @Image export.png
|
|
# ==============================================================================
|
|
def StartExport(books):
|
|
if not books:
|
|
return
|
|
|
|
job = []
|
|
for b in books:
|
|
# Collect all fields for v2.0 Schema
|
|
# Build pages list
|
|
pages = []
|
|
for p in b.Pages:
|
|
pages.append({
|
|
"Image": p.ImageIndex,
|
|
"ImageWidth": p.ImageWidth,
|
|
"ImageHeight": p.ImageHeight,
|
|
"Type": str(p.PageType) if str(p.PageType) != "Story" else ""
|
|
})
|
|
|
|
job.append({
|
|
"ID": str(b.Id),
|
|
"FilePath": b.FilePath,
|
|
# v2.0 fields in schema order
|
|
"Title": b.Title,
|
|
"Series": b.Series,
|
|
"Number": b.Number,
|
|
"Count": b.Count,
|
|
"Volume": b.Volume,
|
|
"AlternateSeries": b.AlternateSeries,
|
|
"AlternateNumber": b.AlternateNumber,
|
|
"AlternateCount": b.AlternateCount if b.AlternateCount > 0 else None,
|
|
"Summary": b.Summary,
|
|
"Notes": b.Notes,
|
|
"Year": b.Year,
|
|
"Month": b.Month,
|
|
"Day": b.Day,
|
|
"Writer": b.Writer,
|
|
"Penciller": b.Penciller,
|
|
"Inker": b.Inker,
|
|
"Colorist": b.Colorist,
|
|
"Letterer": b.Letterer,
|
|
"CoverArtist": b.CoverArtist,
|
|
"Editor": b.Editor,
|
|
"Publisher": b.Publisher,
|
|
"Imprint": b.Imprint,
|
|
"Genre": b.Genre,
|
|
"Web": b.Web if hasattr(b, 'Web') else "",
|
|
"PageCount": b.PageCount,
|
|
"LanguageISO": b.LanguageISO,
|
|
"Format": b.Format,
|
|
"BlackAndWhite": str(b.BlackAndWhite) if str(b.BlackAndWhite) != "Unknown" else "",
|
|
"Manga": str(b.Manga) if str(b.Manga) != "Unknown" else "",
|
|
"Characters": b.Characters,
|
|
"Teams": b.Teams if hasattr(b, 'Teams') else "",
|
|
"Locations": b.Locations,
|
|
"ScanInformation": b.ScanInformation if hasattr(b, 'ScanInformation') else "",
|
|
"StoryArc": b.StoryArc,
|
|
"SeriesGroup": b.SeriesGroup,
|
|
"AgeRating": b.AgeRating,
|
|
"CommunityRating": b.CommunityRating if b.CommunityRating > 0 else None,
|
|
"Rating": b.Rating if hasattr(b, 'Rating') and b.Rating > 0 else None,
|
|
"MainCharacterOrTeam": b.MainCharacterOrTeam,
|
|
"Review": b.Review if hasattr(b, 'Review') else "",
|
|
"Pages": pages,
|
|
# CR-specific data
|
|
"HasBeenRead": b.HasBeenRead,
|
|
"CustomValuesStore": b.CustomValuesStore if b.CustomValuesStore else ""
|
|
})
|
|
|
|
# Save Job to temp file (use .NET for UTF-8 encoding)
|
|
temp = Path.Combine(Path.GetTempPath(), 'cr_sync_job.json')
|
|
File.WriteAllText(temp, serializer.Serialize(job), UTF8Encoding(False))
|
|
|
|
# Execute the External Python 3 Worker
|
|
script = Path.Combine(SCRIPT_DIR, "cr_sync_worker.py")
|
|
# Use .NET Process to launch (IronPython has no subprocess module)
|
|
psi = ProcessStartInfo()
|
|
psi.FileName = PYTHON_EXE
|
|
psi.Arguments = '"%s" export "%s"' % (script, temp)
|
|
psi.UseShellExecute = False
|
|
psi.CreateNoWindow = True
|
|
Process.Start(psi)
|
|
|
|
# ==============================================================================
|
|
# @Name Sync: Read from CBZ
|
|
# @Hook Books
|
|
# @Image import.png
|
|
# ==============================================================================
|
|
def StartImport(books):
|
|
if not books:
|
|
return
|
|
|
|
job = [{"ID": str(b.Id), "FilePath": b.FilePath} for b in books]
|
|
temp = Path.Combine(Path.GetTempPath(), 'cr_read_job.json')
|
|
File.WriteAllText(temp, serializer.Serialize(job), UTF8Encoding(False))
|
|
|
|
script = Path.Combine(SCRIPT_DIR, "cr_sync_worker.py")
|
|
psi = ProcessStartInfo()
|
|
psi.FileName = PYTHON_EXE
|
|
psi.Arguments = '"%s" import "%s"' % (script, temp)
|
|
psi.UseShellExecute = False
|
|
psi.CreateNoWindow = True
|
|
Process.Start(psi)
|
|
|
|
# ==============================================================================
|
|
# @Name Sync: Apply Status
|
|
# @Hook Books
|
|
# @Image apply.png
|
|
# ==============================================================================
|
|
def ApplySyncResults(books):
|
|
results_path = Path.Combine(SCRIPT_DIR, "sync_results.json")
|
|
lock_path = Path.Combine(SCRIPT_DIR, "sync.lock")
|
|
|
|
# Check if worker is still running
|
|
if File.Exists(lock_path):
|
|
MessageBox.Show("Sync is still in progress. Please wait for it to finish.")
|
|
return
|
|
|
|
if not File.Exists(results_path):
|
|
MessageBox.Show("No sync results found. Run a sync process first!")
|
|
return
|
|
|
|
updates = serializer.DeserializeObject(File.ReadAllText(results_path, UTF8Encoding(False)))
|
|
|
|
synced_count = 0
|
|
skipped_count = 0
|
|
failed_count = 0
|
|
imported_count = 0
|
|
|
|
for b in books:
|
|
bid = str(b.Id)
|
|
if bid not in updates:
|
|
continue
|
|
|
|
data = updates[bid]
|
|
|
|
# Check export status - only mark as synced if successful
|
|
if "status" in data:
|
|
status = data["status"]
|
|
if status == "success":
|
|
b.SetCustomValue("ExtSyncStatus", "Synced")
|
|
b.ComicInfoIsDirty = False # Clear "Modified Info" flag
|
|
synced_count += 1
|
|
elif status == "skipped":
|
|
# Skipped means content was identical - already synced
|
|
b.SetCustomValue("ExtSyncStatus", "Synced")
|
|
b.ComicInfoIsDirty = False # Clear "Modified Info" flag
|
|
skipped_count += 1
|
|
else:
|
|
# Failed - don't mark as synced
|
|
failed_count += 1
|
|
continue
|
|
|
|
# If we are in IMPORT mode, the data dict will have metadata tags
|
|
elif "Series" in data:
|
|
# Apply all standard fields (.NET dict uses ContainsKey, not .get())
|
|
if "Title" in data and data["Title"]: b.Title = data["Title"]
|
|
if "Series" in data and data["Series"]: b.Series = data["Series"]
|
|
if "Number" in data and data["Number"]: b.Number = data["Number"]
|
|
if "Volume" in data and data["Volume"]: b.Volume = int(data["Volume"])
|
|
if "Summary" in data and data["Summary"]: b.Summary = data["Summary"]
|
|
if "Notes" in data and data["Notes"]: b.Notes = data["Notes"]
|
|
if "Year" in data and data["Year"]: b.Year = int(data["Year"])
|
|
if "Month" in data and data["Month"]: b.Month = int(data["Month"])
|
|
if "Day" in data and data["Day"]: b.Day = int(data["Day"])
|
|
if "Writer" in data and data["Writer"]: b.Writer = data["Writer"]
|
|
if "Penciller" in data and data["Penciller"]: b.Penciller = data["Penciller"]
|
|
if "Inker" in data and data["Inker"]: b.Inker = data["Inker"]
|
|
if "Colorist" in data and data["Colorist"]: b.Colorist = data["Colorist"]
|
|
if "Letterer" in data and data["Letterer"]: b.Letterer = data["Letterer"]
|
|
if "CoverArtist" in data and data["CoverArtist"]: b.CoverArtist = data["CoverArtist"]
|
|
if "Editor" in data and data["Editor"]: b.Editor = data["Editor"]
|
|
if "Publisher" in data and data["Publisher"]: b.Publisher = data["Publisher"]
|
|
if "Genre" in data and data["Genre"]: b.Genre = data["Genre"]
|
|
if "Format" in data and data["Format"]: b.Format = data["Format"]
|
|
if "Characters" in data and data["Characters"]: b.Characters = data["Characters"]
|
|
if "Locations" in data and data["Locations"]: b.Locations = data["Locations"]
|
|
if "StoryArc" in data and data["StoryArc"]: b.StoryArc = data["StoryArc"]
|
|
if "SeriesGroup" in data and data["SeriesGroup"]: b.SeriesGroup = data["SeriesGroup"]
|
|
if "AgeRating" in data and data["AgeRating"]: b.AgeRating = data["AgeRating"]
|
|
if "MainCharacterOrTeam" in data and data["MainCharacterOrTeam"]: b.MainCharacterOrTeam = data["MainCharacterOrTeam"]
|
|
|
|
# Apply HasBeenRead (now a standard element, already converted to boolean)
|
|
if "HasBeenRead" in data:
|
|
b.HasBeenRead = data["HasBeenRead"]
|
|
|
|
# Apply other custom values
|
|
if "CustomValues" in data:
|
|
custom_vals = data["CustomValues"]
|
|
for key in custom_vals.Keys:
|
|
b.SetCustomValue(key, custom_vals[key])
|
|
|
|
b.SetCustomValue("ExtSyncStatus", "Synced")
|
|
imported_count += 1
|
|
|
|
# Cleanup results file after applying
|
|
try:
|
|
File.Delete(results_path)
|
|
except:
|
|
pass
|
|
|
|
# Build summary message
|
|
msg_parts = []
|
|
if synced_count > 0:
|
|
msg_parts.append(str(synced_count) + " exported")
|
|
if skipped_count > 0:
|
|
msg_parts.append(str(skipped_count) + " unchanged")
|
|
if imported_count > 0:
|
|
msg_parts.append(str(imported_count) + " imported")
|
|
if failed_count > 0:
|
|
msg_parts.append(str(failed_count) + " FAILED (check sync_errors.log)")
|
|
|
|
MessageBox.Show(", ".join(msg_parts) if msg_parts else "No matching books found.") |