forked from NinjaCheetah/NUSGet
Merge major improvements from upstream
This commit is contained in:
@@ -6,8 +6,8 @@ from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class SelectedTitle:
|
||||
# Class to store all components of a selected title to make tracking it easier.
|
||||
class TitleData:
|
||||
# Class to store all data for a Title.
|
||||
tid: str
|
||||
name: str
|
||||
archive_name: str
|
||||
@@ -15,7 +15,6 @@ class SelectedTitle:
|
||||
ticket: bool
|
||||
region: str
|
||||
category: str
|
||||
console: str
|
||||
danger: str
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# "modules/download_dsi.py", licensed under the MIT license
|
||||
# Copyright 2024 NinjaCheetah
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from typing import List, Tuple
|
||||
|
||||
@@ -27,14 +26,13 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
# Create a new libTWLPy Title.
|
||||
title = libTWLPy.Title()
|
||||
# Make a directory for this title if it doesn't exist.
|
||||
title_dir = pathlib.Path(os.path.join(out_folder, tid))
|
||||
if not title_dir.is_dir():
|
||||
title_dir.mkdir()
|
||||
title_dir = out_folder.joinpath(tid)
|
||||
title_dir.mkdir(exist_ok=True)
|
||||
# Announce the title being downloaded, and the version if applicable.
|
||||
if title_version is not None:
|
||||
progress_callback.emit("Downloading title " + tid + " v" + str(title_version) + ", please wait...")
|
||||
progress_callback.emit(f"Downloading title {tid} v{title_version}, please wait...")
|
||||
else:
|
||||
progress_callback.emit("Downloading title " + tid + " vLatest, please wait...")
|
||||
progress_callback.emit(f"Downloading title {tid} vLatest, please wait...")
|
||||
progress_callback.emit(" - Downloading and parsing TMD...")
|
||||
# Download a specific TMD version if a version was specified, otherwise just download the latest TMD.
|
||||
try:
|
||||
@@ -47,25 +45,19 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
except ValueError:
|
||||
return -2
|
||||
# Make a directory for this version if it doesn't exist.
|
||||
version_dir = pathlib.Path(os.path.join(title_dir, str(title_version)))
|
||||
if not version_dir.is_dir():
|
||||
version_dir.mkdir()
|
||||
version_dir = title_dir.joinpath(str(title_version))
|
||||
version_dir.mkdir(exist_ok=True)
|
||||
# Write out the TMD to a file.
|
||||
tmd_out = open(os.path.join(version_dir, "tmd." + str(title_version)), "wb")
|
||||
tmd_out.write(title.tmd.dump())
|
||||
tmd_out.close()
|
||||
version_dir.joinpath(f"tmd.{title_version}").write_bytes(title.tmd.dump())
|
||||
# Use a local ticket, if one exists and "use local files" is enabled.
|
||||
if use_local_chkbox is True and os.path.exists(os.path.join(version_dir, "tik")):
|
||||
if use_local_chkbox and version_dir.joinpath("tik").exists():
|
||||
progress_callback.emit(" - Parsing local copy of Ticket...")
|
||||
local_ticket = open(os.path.join(version_dir, "tik"), "rb")
|
||||
title.load_ticket(local_ticket.read())
|
||||
title.load_ticket(version_dir.joinpath("tik").read_bytes())
|
||||
else:
|
||||
progress_callback.emit(" - Downloading and parsing Ticket...")
|
||||
try:
|
||||
title.load_ticket(libTWLPy.download_ticket(tid))
|
||||
ticket_out = open(os.path.join(version_dir, "tik"), "wb")
|
||||
ticket_out.write(title.ticket.dump())
|
||||
ticket_out.close()
|
||||
version_dir.joinpath("tik").write_bytes(title.ticket.dump())
|
||||
except ValueError:
|
||||
# If libTWLPy returns an error, then no ticket is available. Log this, and disable options requiring a
|
||||
# ticket so that they aren't attempted later.
|
||||
@@ -74,38 +66,27 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
decrypt_contents_enabled = False
|
||||
# Load the content record from the TMD, and download the content it lists. DSi titles only have one content.
|
||||
title.load_content_records()
|
||||
content_file_name = hex(title.tmd.content_record.content_id)[2:]
|
||||
while len(content_file_name) < 8:
|
||||
content_file_name = "0" + content_file_name
|
||||
content_file_name = f"{title.tmd.content_record.content_id:08X}"
|
||||
# Check for a local copy of the current content if "use local files" is enabled, and use it.
|
||||
if use_local_chkbox is True and os.path.exists(os.path.join(version_dir, content_file_name)):
|
||||
if use_local_chkbox and version_dir.joinpath(content_file_name).exists():
|
||||
progress_callback.emit(" - Using local copy of content")
|
||||
local_file = open(os.path.join(version_dir, content_file_name), "rb")
|
||||
content = local_file.read()
|
||||
content = version_dir.joinpath(content_file_name).read_bytes()
|
||||
else:
|
||||
progress_callback.emit(" - Downloading content (Content ID: " + str(title.tmd.content_record.content_id) +
|
||||
", Size: " + str(title.tmd.content_record.content_size) + " bytes)...")
|
||||
progress_callback.emit(f" - Downloading content (Content ID: {title.tmd.content_record.content_id}, Size: "
|
||||
f"{title.tmd.content_record.content_size} bytes)...")
|
||||
content = libTWLPy.download_content(tid, title.tmd.content_record.content_id)
|
||||
progress_callback.emit(" - Done!")
|
||||
# If keep encrypted contents is on, write out each content after its downloaded.
|
||||
# If keep encrypted contents is on, write out the content after its downloaded.
|
||||
if keep_enc_chkbox is True:
|
||||
enc_content_out = open(os.path.join(version_dir, content_file_name), "wb")
|
||||
enc_content_out.write(content)
|
||||
enc_content_out.close()
|
||||
version_dir.joinpath(content_file_name).write_bytes(content)
|
||||
title.content.content = content
|
||||
# If decrypt local contents is still true, decrypt each content and write out the decrypted file.
|
||||
# If decrypt local contents is still true, decrypt the content and write out the decrypted file.
|
||||
if decrypt_contents_enabled is True:
|
||||
try:
|
||||
progress_callback.emit(" - Decrypting content (Content ID: " + str(title.tmd.content_record.content_id)
|
||||
+ ")...")
|
||||
progress_callback.emit(f" - Decrypting content (Content ID: {title.tmd.content_record.content_id})...")
|
||||
dec_content = title.get_content()
|
||||
content_file_name = hex(title.tmd.content_record.content_id)[2:]
|
||||
while len(content_file_name) < 8:
|
||||
content_file_name = "0" + content_file_name
|
||||
content_file_name = content_file_name + ".app"
|
||||
dec_content_out = open(os.path.join(version_dir, content_file_name), "wb")
|
||||
dec_content_out.write(dec_content)
|
||||
dec_content_out.close()
|
||||
content_file_name = f"{title.tmd.content_record.content_id:08X}.app"
|
||||
version_dir.joinpath(content_file_name).write_bytes(dec_content)
|
||||
except ValueError:
|
||||
# If libWiiPy throws an error during decryption, return code -3. This should only be possible if using
|
||||
# local encrypted contents that have been altered at present.
|
||||
@@ -118,14 +99,12 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
# Use a typed TAD name if there is one, and auto generate one based on the TID and version if there isn't.
|
||||
progress_callback.emit("Packing TAD...")
|
||||
if tad_file_name != "" and tad_file_name is not None:
|
||||
if tad_file_name[-4:] != ".tad":
|
||||
tad_file_name = tad_file_name + ".tad"
|
||||
if tad_file_name[-4:].lower() != ".tad":
|
||||
tad_file_name += ".tad"
|
||||
else:
|
||||
tad_file_name = tid + "-v" + str(title_version) + ".tad"
|
||||
tad_file_name = f"{tid}-v{title_version}.tad"
|
||||
# Have libTWLPy dump the TAD, and write that data out.
|
||||
file = open(os.path.join(version_dir, tad_file_name), "wb")
|
||||
file.write(title.dump_tad())
|
||||
file.close()
|
||||
version_dir.joinpath(tad_file_name).write_bytes(title.dump_tad())
|
||||
progress_callback.emit("Download complete!")
|
||||
# This is where the variables come in. If the state of these variables doesn't match the user's choice by this
|
||||
# point, it means that they enabled decryption or TAD packing for a title that doesn't have a ticket. Return
|
||||
@@ -134,12 +113,14 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def run_nus_download_dsi_batch(out_folder: pathlib.Path, titles: List[Tuple[str, str, str]], pack_tad_chkbox: bool, keep_enc_chkbox: bool,
|
||||
decrypt_contents_chkbox: bool, use_local_chkbox: bool, progress_callback=None):
|
||||
def run_nus_download_dsi_batch(out_folder: pathlib.Path, titles: List[Tuple[str, str, str]], pack_tad_chkbox: bool,
|
||||
keep_enc_chkbox: bool, decrypt_contents_chkbox: bool, use_local_chkbox: bool,
|
||||
progress_callback=None):
|
||||
for title in titles:
|
||||
result = run_nus_download_dsi(out_folder, title[0], title[1], pack_tad_chkbox, keep_enc_chkbox, decrypt_contents_chkbox, use_local_chkbox, f"{title[2]}-{title[1]}.tad", progress_callback)
|
||||
result = run_nus_download_dsi(out_folder, title[0], title[1], pack_tad_chkbox, keep_enc_chkbox,
|
||||
decrypt_contents_chkbox, use_local_chkbox, f"{title[2]}-{title[1]}.tad",
|
||||
progress_callback)
|
||||
if result != 0:
|
||||
return result
|
||||
|
||||
progress_callback.emit(f"Batch download finished.")
|
||||
return 0
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# "modules/download_wii.py", licensed under the MIT license
|
||||
# Copyright 2024 NinjaCheetah
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from typing import List, Tuple
|
||||
from .tkey import find_tkey
|
||||
@@ -12,7 +11,6 @@ from libWiiPy.title.ticket import _TitleLimit
|
||||
def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_wad_chkbox: bool, keep_enc_chkbox: bool,
|
||||
decrypt_contents_chkbox: bool, wiiu_nus_chkbox: bool, use_local_chkbox: bool,
|
||||
repack_vwii_chkbox: bool, patch_ios: bool, wad_file_name: str, progress_callback=None):
|
||||
#print(version)
|
||||
# Actual NUS download function that runs in a separate thread.
|
||||
# Immediately knock out any invalidly formatted Title IDs.
|
||||
if len(tid) != 16:
|
||||
@@ -31,14 +29,13 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
# Create a new libWiiPy Title.
|
||||
title = libWiiPy.title.Title()
|
||||
# Make a directory for this title if it doesn't exist.
|
||||
title_dir = pathlib.Path(os.path.join(out_folder, tid))
|
||||
if not title_dir.is_dir():
|
||||
title_dir.mkdir()
|
||||
title_dir = out_folder.joinpath(tid)
|
||||
title_dir.mkdir(exist_ok=True)
|
||||
# Announce the title being downloaded, and the version if applicable.
|
||||
if title_version is not None:
|
||||
progress_callback.emit("Downloading title " + tid + " v" + str(title_version) + ", please wait...")
|
||||
progress_callback.emit(f"Downloading title {tid} v{title_version}, please wait...")
|
||||
else:
|
||||
progress_callback.emit("Downloading title " + tid + " vLatest, please wait...")
|
||||
progress_callback.emit(f"Downloading title {tid} vLatest, please wait...")
|
||||
progress_callback.emit(" - Downloading and parsing TMD...")
|
||||
# Download a specific TMD version if a version was specified, otherwise just download the latest TMD.
|
||||
try:
|
||||
@@ -51,26 +48,20 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
except ValueError:
|
||||
return -2
|
||||
# Make a directory for this version if it doesn't exist.
|
||||
version_dir = pathlib.Path(os.path.join(title_dir, str(title_version)))
|
||||
if not version_dir.is_dir():
|
||||
version_dir.mkdir()
|
||||
version_dir = title_dir.joinpath(str(title_version))
|
||||
version_dir.mkdir(exist_ok=True)
|
||||
# Write out the TMD to a file.
|
||||
tmd_out = open(os.path.join(version_dir, "tmd." + str(title_version)), "wb")
|
||||
tmd_out.write(title.tmd.dump())
|
||||
tmd_out.close()
|
||||
version_dir.joinpath(f"tmd.{title_version}").write_bytes(title.tmd.dump())
|
||||
# Use a local ticket, if one exists and "use local files" is enabled.
|
||||
forge_ticket = False
|
||||
if use_local_chkbox is True and os.path.exists(os.path.join(version_dir, "tik")):
|
||||
if use_local_chkbox and version_dir.joinpath("tik").exists():
|
||||
progress_callback.emit(" - Parsing local copy of Ticket...")
|
||||
local_ticket = open(os.path.join(version_dir, "tik"), "rb")
|
||||
title.load_ticket(local_ticket.read())
|
||||
title.load_ticket(version_dir.joinpath("tik").read_bytes())
|
||||
else:
|
||||
progress_callback.emit(" - Downloading and parsing Ticket...")
|
||||
try:
|
||||
title.load_ticket(libWiiPy.title.download_ticket(tid, wiiu_endpoint=wiiu_nus_enabled))
|
||||
ticket_out = open(os.path.join(version_dir, "tik"), "wb")
|
||||
ticket_out.write(title.ticket.dump())
|
||||
ticket_out.close()
|
||||
version_dir.joinpath("tik").write_bytes(title.ticket.dump())
|
||||
except ValueError:
|
||||
# If libWiiPy returns an error, then no ticket is available. Try to forge a ticket after we download the
|
||||
# content.
|
||||
@@ -80,31 +71,22 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
title.load_content_records()
|
||||
content_list = []
|
||||
for content in range(len(title.tmd.content_records)):
|
||||
# Generate the correct file name by converting the content ID into hex, minus the 0x, and then appending
|
||||
# that to the end of 000000. I refuse to believe there isn't a better way to do this here and in libWiiPy.
|
||||
content_file_name = hex(title.tmd.content_records[content].content_id)[2:]
|
||||
while len(content_file_name) < 8:
|
||||
content_file_name = "0" + content_file_name
|
||||
# Generate the correct file name by converting the content ID into hex.
|
||||
content_file_name = f"{title.tmd.content_records[content].content_id:08X}"
|
||||
# Check for a local copy of the current content if "use local files" is enabled, and use it.
|
||||
if use_local_chkbox is True and os.path.exists(os.path.join(version_dir,
|
||||
content_file_name)):
|
||||
progress_callback.emit(" - Using local copy of content " + str(content + 1) + " of " +
|
||||
str(len(title.tmd.content_records)))
|
||||
local_file = open(os.path.join(version_dir, content_file_name), "rb")
|
||||
content_list.append(local_file.read())
|
||||
if use_local_chkbox is True and version_dir.joinpath(content_file_name).exists():
|
||||
progress_callback.emit(f" - Using local copy of content {content + 1} of {len(title.tmd.content_records)}")
|
||||
content_list.append(version_dir.joinpath(content_file_name).read_bytes())
|
||||
else:
|
||||
progress_callback.emit(" - Downloading content " + str(content + 1) + " of " +
|
||||
str(len(title.tmd.content_records)) + " (Content ID: " +
|
||||
str(title.tmd.content_records[content].content_id) + ", Size: " +
|
||||
str(title.tmd.content_records[content].content_size) + " bytes)...")
|
||||
progress_callback.emit(f" - Downloading content {content + 1} of {len(title.tmd.content_records)} "
|
||||
f"(Content ID: {title.tmd.content_records[content].content_id}, Size: "
|
||||
f"{title.tmd.content_records[content].content_size} bytes)...")
|
||||
content_list.append(libWiiPy.title.download_content(tid, title.tmd.content_records[content].content_id,
|
||||
wiiu_endpoint=wiiu_nus_enabled))
|
||||
progress_callback.emit(" - Done!")
|
||||
# If keep encrypted contents is on, write out each content after its downloaded.
|
||||
if keep_enc_chkbox is True:
|
||||
enc_content_out = open(os.path.join(version_dir, content_file_name), "wb")
|
||||
enc_content_out.write(content_list[content])
|
||||
enc_content_out.close()
|
||||
version_dir.joinpath(content_file_name).write_bytes(content_list[content])
|
||||
title.content.content_list = content_list
|
||||
# Try to forge a Ticket, if a common one wasn't available.
|
||||
if forge_ticket is True:
|
||||
@@ -143,17 +125,11 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
if decrypt_contents_enabled is True:
|
||||
try:
|
||||
for content in range(len(title.tmd.content_records)):
|
||||
progress_callback.emit(" - Decrypting content " + str(content + 1) + " of " +
|
||||
str(len(title.tmd.content_records)) + " (Content ID: " +
|
||||
str(title.tmd.content_records[content].content_id) + ")...")
|
||||
progress_callback.emit(f" - Decrypting content {content + 1} of {len(title.tmd.content_records)} "
|
||||
f"(Content ID: {title.tmd.content_records[content].content_id})...")
|
||||
dec_content = title.get_content_by_index(content)
|
||||
content_file_name = hex(title.tmd.content_records[content].content_id)[2:]
|
||||
while len(content_file_name) < 8:
|
||||
content_file_name = "0" + content_file_name
|
||||
content_file_name = content_file_name + ".app"
|
||||
dec_content_out = open(os.path.join(version_dir, content_file_name), "wb")
|
||||
dec_content_out.write(dec_content)
|
||||
dec_content_out.close()
|
||||
content_file_name = f"{title.tmd.content_records[content].content_id:08X}.app"
|
||||
version_dir.joinpath(content_file_name).write_bytes(dec_content)
|
||||
except ValueError:
|
||||
# If libWiiPy throws an error during decryption, return code -3. This should only be possible if using
|
||||
# local encrypted contents that have been altered at present.
|
||||
@@ -165,8 +141,7 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
# vWii mode. (vWii mode does not have access to the vWii key, only Wii U mode has that.)
|
||||
if repack_vwii_chkbox is True and (tid[3] == "7" or tid[7] == "7"):
|
||||
progress_callback.emit(" - Re-encrypting Title Key with the common key...")
|
||||
title_key_dec = title.ticket.get_title_key()
|
||||
title_key_common = libWiiPy.title.encrypt_title_key(title_key_dec, 0, title.tmd.title_id)
|
||||
title_key_common = libWiiPy.title.encrypt_title_key(title.ticket.get_title_key(), 0, title.tmd.title_id)
|
||||
title.ticket.common_key_index = 0
|
||||
title.ticket.title_key_enc = title_key_common
|
||||
# Get the WAD certificate chain, courtesy of libWiiPy.
|
||||
@@ -175,10 +150,10 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
# Use a typed WAD name if there is one, and auto generate one based on the TID and version if there isn't.
|
||||
progress_callback.emit(" - Packing WAD...")
|
||||
if wad_file_name != "" and wad_file_name is not None:
|
||||
if wad_file_name[-4:] != ".wad":
|
||||
wad_file_name = wad_file_name + ".wad"
|
||||
if wad_file_name[-4:].lower() != ".wad":
|
||||
wad_file_name += ".wad"
|
||||
else:
|
||||
wad_file_name = tid + "-v" + str(title_version) + ".wad"
|
||||
wad_file_name = f"{tid}-v{title_version}.wad"
|
||||
# If enabled (after we make sure it's an IOS), apply all main IOS patches.
|
||||
if patch_ios and (tid[:8] == "00000001" and int(tid[-2:], 16) > 2):
|
||||
progress_callback.emit(" - Patching IOS...")
|
||||
@@ -191,9 +166,7 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
progress_callback.emit(" - No patches could be applied! Is this a stub IOS?")
|
||||
title = ios_patcher.dump()
|
||||
# Have libWiiPy dump the WAD, and write that data out.
|
||||
file = open(os.path.join(version_dir, wad_file_name), "wb")
|
||||
file.write(title.dump_wad())
|
||||
file.close()
|
||||
version_dir.joinpath(wad_file_name).write_bytes(title.dump_wad())
|
||||
progress_callback.emit("Download complete!")
|
||||
# This is where the variables come in. If the state of these variables doesn't match the user's choice by this
|
||||
# point, it means that they enabled decryption or WAD packing for a title that doesn't have a ticket. Return
|
||||
@@ -202,13 +175,15 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def run_nus_download_wii_batch(out_folder: pathlib.Path, titles: List[Tuple[str, str, str]], pack_wad_chkbox: bool, keep_enc_chkbox: bool,
|
||||
decrypt_contents_chkbox: bool, wiiu_nus_chkbox: bool, use_local_chkbox: bool,
|
||||
repack_vwii_chkbox: bool, patch_ios: bool, progress_callback=None):
|
||||
def run_nus_download_wii_batch(out_folder: pathlib.Path, titles: List[Tuple[str, str, str]], pack_wad_chkbox: bool,
|
||||
keep_enc_chkbox: bool, decrypt_contents_chkbox: bool, wiiu_nus_chkbox: bool,
|
||||
use_local_chkbox: bool, repack_vwii_chkbox: bool, patch_ios: bool,
|
||||
progress_callback=None):
|
||||
for title in titles:
|
||||
result = run_nus_download_wii(out_folder, title[0], title[1], pack_wad_chkbox, keep_enc_chkbox, decrypt_contents_chkbox, wiiu_nus_chkbox, use_local_chkbox, repack_vwii_chkbox, patch_ios, f"{title[2]}-{title[1]}.wad", progress_callback)
|
||||
result = run_nus_download_wii(out_folder, title[0], title[1], pack_wad_chkbox, keep_enc_chkbox,
|
||||
decrypt_contents_chkbox, wiiu_nus_chkbox, use_local_chkbox, repack_vwii_chkbox,
|
||||
patch_ios, f"{title[2]}-{title[1]}.wad", progress_callback)
|
||||
if result != 0:
|
||||
return result
|
||||
|
||||
progress_callback.emit(f"Batch download finished.")
|
||||
return 0
|
||||
|
||||
136
modules/tree.py
Normal file
136
modules/tree.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# "modules/tree.py", licensed under the MIT license
|
||||
# Copyright 2024 NinjaCheetah
|
||||
|
||||
from modules.core import TitleData
|
||||
from PySide6.QtCore import QAbstractItemModel, QModelIndex, Qt
|
||||
from PySide6.QtGui import QIcon
|
||||
|
||||
|
||||
class TreeItem:
|
||||
def __init__(self, data, parent=None, metadata=None):
|
||||
self.data = data
|
||||
self.parent = parent
|
||||
self.children = []
|
||||
self.metadata = metadata # Store hidden metadata
|
||||
|
||||
def add_child(self, item):
|
||||
self.children.append(item)
|
||||
|
||||
def child(self, row):
|
||||
return self.children[row]
|
||||
|
||||
def child_count(self):
|
||||
return len(self.children)
|
||||
|
||||
def column_count(self):
|
||||
return len(self.data)
|
||||
|
||||
def data_at(self, column):
|
||||
if 0 <= column < len(self.data):
|
||||
return self.data[column]
|
||||
return None
|
||||
|
||||
def row(self):
|
||||
if self.parent:
|
||||
return self.parent.children.index(self)
|
||||
return 0
|
||||
|
||||
|
||||
class NUSGetTreeModel(QAbstractItemModel):
|
||||
def __init__(self, data, parent=None, root_name=""):
|
||||
super().__init__(parent)
|
||||
self.root_item = TreeItem([root_name])
|
||||
self.setup_model_data(data, self.root_item)
|
||||
|
||||
def setup_model_data(self, title, parent):
|
||||
if isinstance(title, dict):
|
||||
for key, value in title.items():
|
||||
if isinstance(value, list):
|
||||
key_item = TreeItem([key, ""], parent)
|
||||
parent.add_child(key_item)
|
||||
for entry in value:
|
||||
tid = entry.get("TID")
|
||||
name = entry.get("Name")
|
||||
versions = entry.get("Versions", {})
|
||||
if tid:
|
||||
tid_item = TreeItem([f"{tid} - {name}", ""], key_item)
|
||||
key_item.add_child(tid_item)
|
||||
for region, version_list in versions.items():
|
||||
region_item = TreeItem([region, ""], tid_item)
|
||||
tid_item.add_child(region_item)
|
||||
for version in version_list:
|
||||
danger = entry.get("Danger") if entry.get("Danger") is not None else ""
|
||||
metadata = TitleData(entry.get("TID"), entry.get("Name"), entry.get("Archive Name"),
|
||||
version, entry.get("Ticket"), region, key, danger)
|
||||
public_versions = entry.get("Public Versions")
|
||||
if public_versions is not None:
|
||||
try:
|
||||
public_ver = public_versions[str(version)]
|
||||
version_str = f"v{version} ({public_ver})"
|
||||
except KeyError:
|
||||
version_str = f"v{version}"
|
||||
else:
|
||||
version_str = f"v{version}"
|
||||
version_item = TreeItem([version_str, ""], region_item, metadata)
|
||||
region_item.add_child(version_item)
|
||||
|
||||
def rowCount(self, parent=QModelIndex()):
|
||||
if parent.isValid():
|
||||
parent_item = parent.internalPointer()
|
||||
else:
|
||||
parent_item = self.root_item
|
||||
return parent_item.child_count()
|
||||
|
||||
def columnCount(self, parent=QModelIndex()):
|
||||
return self.root_item.column_count()
|
||||
|
||||
def data(self, index, role=Qt.DisplayRole):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
item = index.internalPointer()
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
item = index.internalPointer()
|
||||
return item.data_at(index.column())
|
||||
|
||||
if role == Qt.DecorationRole and index.column() == 0:
|
||||
# Check for icons based on the "Ticket" metadata only at the TID level
|
||||
if item.parent and item.parent.data_at(0) == "System":
|
||||
if item.metadata and "Ticket" in item.metadata:
|
||||
if item.metadata["Ticket"]:
|
||||
return QIcon.fromTheme("dialog-ok") # Checkmark icon
|
||||
else:
|
||||
return QIcon.fromTheme("dialog-cancel") # X icon
|
||||
return None
|
||||
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
||||
return self.root_item.data_at(section)
|
||||
return None
|
||||
|
||||
def index(self, row, column, parent=QModelIndex()):
|
||||
if not self.hasIndex(row, column, parent):
|
||||
return QModelIndex()
|
||||
|
||||
if not parent.isValid():
|
||||
parent_item = self.root_item
|
||||
else:
|
||||
parent_item = parent.internalPointer()
|
||||
|
||||
child_item = parent_item.child(row)
|
||||
if child_item:
|
||||
return self.createIndex(row, column, child_item)
|
||||
return QModelIndex()
|
||||
|
||||
def parent(self, index):
|
||||
if not index.isValid():
|
||||
return QModelIndex()
|
||||
|
||||
child_item = index.internalPointer()
|
||||
parent_item = child_item.parent
|
||||
|
||||
if parent_item == self.root_item:
|
||||
return QModelIndex()
|
||||
|
||||
return self.createIndex(parent_item.row(), 0, parent_item)
|
||||
Reference in New Issue
Block a user