Exemplo n.º 1
0
 def __init__(self, name, categoryId, id=None, testing=False, **kwargs):
     self.name = name
     self.categoryId = categoryId
     if id is None:
         id = str(id_gen())
     self.id = id
     if testing:
         self.testing = testing
Exemplo n.º 2
0
 def __init__(self, name, attributes, id=None, testing=False, **kwargs):
     self.name = name
     self.attributes = attributes
     if id is None:
         id = str(id_gen())
     self.id = id
     if testing:
         self.testing = testing
Exemplo n.º 3
0
 def __init__(self, name, id=None, items=None, testing=False, **kwargs):
     self.name = name
     if id is None:
         id = str(id_gen())
     self.id = id
     if items is None:
         items = []
     self.items = items
     if testing:
         self.testing = testing
Exemplo n.º 4
0
 def __init__(self, itemId, specs, id=None, testing=False, **kwargs):
     self.itemId = itemId
     if id is None:
         id = str(id_gen())
     self.id = id
     self.specs = specs
     if testing:
         self.testing = testing
     item = getItem(itemId)
     category = getCategory(item.getCategoryId())
     if not specs:
         for attr in category.getAttributes():
             self.specs[attr] = None
Exemplo n.º 5
0
 def setSpec(self,
             attr,
             value,
             override=False,
             id=str(id_gen()),
             testing=False,
             **kwargs):
     if attr not in getCategory(getItem(
             self.itemId).getCategoryId()).getAttributes():
         raise InvalidAttributeError
     elif self.specs[attr] is not None and not override:
         raise OverrideError
     else:
         self.specs[attr] = value
     updateChecklistItem(self)
Exemplo n.º 6
0
 def __init__(self,
              name,
              id=None,
              login=None,
              pin=None,
              checklists=None,
              admin=False,
              testing=False,
              **kwargs):
     self.name = name
     if checklists is None:
         checklists = {}
     self.checklists = checklists
     if id is None:
         id = str(id_gen())
     self.id = id
     self.pin = pin
     self.admin = admin
     if testing:
         self.testing = testing
Exemplo n.º 7
0
def cli_main(maindir=""):
    """implement pynps cli interface"""
    runing_from = get_pyinstaller()
    if get_system() == 'Linux':
        base_folder = os.getenv('HOME')
        CONFIGFOLDER = f"{base_folder}/.config/pyNPS"
        config_file = f"{CONFIGFOLDER}/settings.ini"
    elif get_system() == 'Windows':
        if runing_from == 'pi-onefile':
            # one file will have all the related directories in the same folder as the .exe
            base_folder = maindir
            CONFIGFOLDER = f"{base_folder}/pynps_config/"
            config_file = f"{CONFIGFOLDER}/settings.ini"
        elif runing_from in ['python', 'pi-onefolder']:
            # python and one-fodler will have related directories inside Documents
            # python won't come undled with wget.exe and pkg2zip.exe!
            base_folder = os.path.expanduser("~")
            if runing_from == 'python':
                CONFIGFOLDER = f"{base_folder}/Documents/pyNPS"
            else:
                CONFIGFOLDER = f"{base_folder}/Documents/pyNPSbin"
            config_file = f"{CONFIGFOLDER}/settings.ini"

    # create conf file
    if os.path.isfile(config_file) == False:
        create_config(config_file, CONFIGFOLDER, base_folder)
        if CONFIGFOLDER != "":
            if runing_from != 'pi-onefolder':
                create_folder(CONFIGFOLDER + "/lib/")

    # read conf file
    config = configparser.ConfigParser()
    config.read(config_file)

    # test sections
    if sorted(config.sections()) != sorted([
            'pyNPS', 'PSV_Links', 'PSP_Links', 'PSX_Links', 'PSM_Links',
            'PS3_Links', 'BinaryLocations'
    ]):
        rich.print(
            "Error: config file is missing sections: you need the following sections in your config file: 'PSV_Links', 'PSP_Links', 'PSX_Links', 'PSM_Links', 'PS3_Links', 'BinaryLocations'",
            style='red on black')
        sys.exit(1)
    if sorted(list(config["PSV_Links"])) != sorted(
        ['games', 'dlcs', 'themes', 'updates', 'demos']):
        rich.print(
            "Error: config file is missing options in the PSV_Links section: you need the following options in your PSV_Links section: 'games', 'dlcs', 'themes', 'updates', 'demos'",
            style='red on black')
        sys.exit(1)
    if sorted(list(config["PSP_Links"])) != sorted(
        ['games', 'dlcs', 'themes', 'updates']):
        rich.print(
            "Error: config file is missing options in the PSP_Links section: you need the following options in your PSP_Links section: 'games', 'dlcs', 'themes', 'updates'",
            style='red on black')
        sys.exit(1)
    if sorted(list(config["PSX_Links"])) != sorted(['games']):
        rich.print(
            "Error: config file is missing options in the PSX_Links section: you need the following options in your PSX_Links section: 'games'",
            style='red on black')
        sys.exit(1)
    if sorted(list(config["PSM_Links"])) != sorted(['games']):
        rich.print(
            "Error: config file is missing options in the PSM_Links section: you need the following options in your PSM_Links section: 'games'",
            style='red on black')
        sys.exit(1)
    if sorted(list(config["PS3_Links"])) != sorted(
        ['games', 'dlcs', 'themes', 'demos', 'avatars']):
        rich.print(
            "Error: config file is missing options in the PS3_Links section: you need the following options in your PS3_Links section: 'games', 'dlcs', 'themes', 'demos', 'avatars'",
            style='red on black')
        sys.exit(1)

    # making vars
    DBFOLDER = fix_folder_syntax(config['pyNPS']['databasefolder'], maindir)
    DLFOLDER = fix_folder_syntax(config['pyNPS']['downloadfolder'], maindir)
    PKG2ZIP = fix_folder_syntax(config['BinaryLocations']['pkg2zip_location'],
                                maindir)
    WGET = fix_folder_syntax(config['BinaryLocations']['wget_location'],
                             maindir)

    # tests existence of pkg2zip
    PKG2ZIP = check_pkg2zip(PKG2ZIP, CONFIGFOLDER)
    if PKG2ZIP == False:
        rich.print(
            "You don't have a valid pkg2zip installation or binary in your system, extraction will be skipped. PS3 games don't need pkg2zip",
            style='dark_orange')

    # tests existence of wget
    WGET = check_wget(WGET, CONFIGFOLDER)
    if WGET == False:
        rich.print(
            "Error: you don't have a valid wget installation or binary in your system, this program can't work without it",
            style='red on black')
        sys.exit(1)

    # makin dicts for links
    database_psv_links = {}
    for key in config["PSV_Links"]:
        database_psv_links[key] = config["PSV_Links"][key]

    database_psp_links = {}
    for key in config["PSP_Links"]:
        database_psp_links[key] = config["PSP_Links"][key]

    database_psx_links = {}
    for key in config["PSX_Links"]:
        database_psx_links[key] = config["PSX_Links"][key]

    database_psm_links = {}
    for key in config["PSM_Links"]:
        database_psm_links[key] = config["PSM_Links"][key]

    database_ps3_links = {}
    for key in config["PS3_Links"]:
        database_ps3_links[key] = config["PS3_Links"][key]

    # create args
    args, parser = create_args()

    limit_rate = args.limit_rate
    keepkg = args.keepkg
    system = args.console
    cso_factor = args.compress_cso

    if args.resume_session:
        # in this case args.search will be considered a tag to fast resume a session

        input_tag = args.search
        if input_tag is not None:
            if input_tag.isalnum() is False:
                rich.print("Tags can only be alphanumeric without spaces",
                           style='dark_orange')
                input_tag = None

        ##load download db
        with SqliteDict(f"{DBFOLDER}/downloads.db",
                        autocommit=False) as database:
            try:
                db = database['resumes']
                if len(db) == 0:
                    raise
            except:
                rich.print("There are no saved download sessions to resume",
                           style='dark_orange')
                sys.exit(0)

        checker = next(
            (item for item in db if item['session_tag'] == input_tag), None)

        ## if tag is prvided and there's a match in the db
        if input_tag is not None and checker is not None:
            # has something in db
            session = checker

        elif checker is None or input_tag is None:
            if input_tag is not None:
                rich.print("There's no such tag in the download sessions",
                           style='dark_orange')

                # validation resume input
                class Check_resume_input_y_n(Validator):
                    def validate(self, document):
                        text = document.text
                        if len(text) > 0:
                            if text.lower() not in ['y', 'n']:
                                # break
                                raise ValidationError(
                                    message='Use "y" for yes and "n" for no',
                                    cursor_position=0)
                        else:
                            raise ValidationError(
                                message=
                                'Enter something or press Ctrl+C to close.',
                                cursor_position=0)

                try:
                    yn_check = prompt(
                        "Wanna see all currently download sessions? [y/n]: ",
                        validator=Check_resume_input_y_n())
                    if yn_check.lower() != "y":
                        raise
                except KeyboardInterrupt:
                    rich.print('Interrupted by user', style='bright_black')
                    sys.exit(0)
                except:
                    rich.print('Interrupted by user', style='bright_black')
                    sys.exit(0)

            p_db = []
            for index_file, i in enumerate(db):
                i["Index"] = index_file + 1
                p_db.append(i)

            process_resumes(p_db)

            # validating input
            class Check_resume_input(Validator):
                def validate(self, document):
                    text = document.text
                    if len(text) > 0:
                        if text.isdigit():

                            # test if number being typed is bigger than the biggest number from game list
                            num_p = len(db)
                            if num_p == 1 and text != "1":
                                raise ValidationError(
                                    message="Your only option is to type 1.",
                                    cursor_position=0)
                            if int(text) > num_p:
                                raise ValidationError(
                                    message=
                                    f"There are no entries past {num_p}.")
                        else:
                            raise ValidationError(
                                message="Please only use numbers",
                                cursor_position=0)
                    else:
                        raise ValidationError(
                            message=
                            'Enter something or press Ctrl+C to close. Press "h" for help.',
                            cursor_position=0)

            try:
                session_index = prompt(
                    "Enter the number for the download session you want to resume: ",
                    validator=Check_resume_input())
            except KeyboardInterrupt:
                rich.print('Interrupted by user', style='bright_black')
                sys.exit(0)
            except:
                rich.print('Interrupted by user', style='bright_black')
                sys.exit(0)

            session = db[int(session_index) - 1]

        files_to_download = session["session_dict"]

    else:

        what_to_dl = {
            "games": args.games,
            "dlcs": args.dlcs,
            "themes": args.themes,
            "updates": args.updates,
            "demos": args.demos,
            "avatars": args.avatars
        }

        if set(what_to_dl.values()) == set([False]):
            for i in what_to_dl:
                what_to_dl[i] = True

        if args.update == True:
            if [args.region, args.search, args.eboot, args.compress_cso
                ] != [None, None, False, None]:
                # TODO let the user search while updating the database
                rich.print(
                    "Error: you can't search while updating the database",
                    style='red on black')
                sys.exit(1)

            what_to_up = [x for x in what_to_dl if what_to_dl[x] == True]

            for i in system:
                fillterm(
                    f"[green on black][UPDATEDB][/green on black][green] Updating {variables.FULL_SYSTEM_NAME[i]} database"
                )
                if i == "PSV":
                    db = database_psv_links
                elif i == "PSP":
                    db = database_psp_links
                elif i == "PSX":
                    db = database_psx_links
                elif i == "PSM":
                    db = database_psm_links
                elif i == "PS3":
                    db = database_ps3_links

                # parsing supported
                what_to_up_parsed = [x for x in what_to_up if x in db.keys()]

                if len(what_to_up_parsed) > 0:
                    updatedb(db, i, DBFOLDER, WGET, what_to_up_parsed)
                else:
                    rich.print("Nothing to do!", style='green')

            fillterm()

            if get_xmas():
                rich.print(f"Done! [red]Happy Holidays!!![/red] 🎄✨",
                           style='green')
            else:
                rich.print(f"Done!", style='green')

            sys.exit(0)

        elif args.update == False and args.search is None:
            rich.print(
                "Error: No search term provided, you need to search for something, use -h for help",
                style='red on black')
            sys.exit(1)

        # checking for database's existense:
        if os.path.isfile(f"{DBFOLDER}/pynps.db") is False:
            rich.print(
                "Error: theres no database in your system, please update your database and try again, use -h for help",
                style='red on black')
            sys.exit(1)

        # check region
        if args.region == None:
            reg = ["usa", "eur", "jap", "asia", "int"]
        else:
            reg = args.region

        # maybe_download = []
        maybe_download = search_db(system, what_to_dl, args.search, reg,
                                   args.sort, DBFOLDER)

        # test if the result isn't empty
        if len(maybe_download) == 0:
            rich.print(
                "No results found, try searching for something else or updating your database",
                style='dark_orange')
            sys.exit(0)

        # adding indexes to maybe_download
        for i in range(0, len(maybe_download)):
            # maybe_download[i]["Index"] = str(i)
            maybe_download[i]["Index"] = str(i + 1)

        # print possible mathes to the user
        if len(maybe_download) > 1:
            if args.print is True:
                for i in maybe_download:
                    print(
                        f"{i['Index']} {i['System']} | {i['Title ID']} | {variables.REGION_DICT[i['Region']]} | {i['Type']} | {i['Name']} | [{file_size(i['File Size'])}]"
                        .encode("utf-8"))
                sys.exit(0)
            fillterm("[green][SEARCH] here are the matches[/green]")
            process_search(maybe_download)

        if args.noconfirm is False:
            # validating input
            class Check_game_input(Validator):
                def validate(self, document):
                    text = document.text
                    if len(text) > 0:
                        # test if number being typed is bigger than the biggest number from game list
                        num_p = len(maybe_download)
                        if num_p == 1 and text != "1":
                            raise ValidationError(
                                message="Your only option is to type 1.",
                                cursor_position=0)
                        text_processed = text.replace("-", " ").replace(
                            ",", " ").split(" ")
                        last_texttext_processed = [
                            x for x in text_processed if x != ""
                        ]
                        last_text = text_processed[-1]

                        if "0" in text_processed:
                            raise ValidationError(
                                message='Zero is an invalid entry.')

                        if last_text.isdigit():
                            last_text = int(last_text)
                            if last_text > num_p:
                                raise ValidationError(
                                    message=
                                    f"There are no entries past {num_p}.")

                        if text.startswith("-") or text.startswith(","):
                            # break
                            raise ValidationError(
                                message=
                                'Start the input with a number. Press "h" for help.',
                                cursor_position=0)

                        if "--" in text or ",," in text or ",-" in text or "-," in text:
                            raise ValidationError(
                                message=
                                'Use only one symbol to separate numbers. Press "h" for help.'
                            )

                        text = text.replace("-", "").replace(",", "")
                        if text.isdigit() is False and text != "h":
                            raise ValidationError(
                                message='Do not use leters. Press "h" for help.'
                            )
                    else:
                        raise ValidationError(
                            message=
                            'Enter something or press Ctrl+C to close. Press "h" for help.',
                            cursor_position=0)

            if len(maybe_download) > 1:
                try:
                    index_to_download_raw = prompt(
                        "Enter the number for what you want to download, you can enter multiple numbers using commas: ",
                        validator=Check_game_input())
                except KeyboardInterrupt:
                    rich.print('Interrupted by user', style='bright_black')
                    sys.exit(0)
                except:
                    rich.print('Interrupted by user', style='bright_black')
                    sys.exit(0)
            else:
                index_to_download_raw = "1"

            # provides help
            if index_to_download_raw.lower() == "h":
                rich.print("\tSuppose you have 10 files to select from:",
                           style='bright_black')
                rich.print("\tTo download file 2, you type: 2",
                           style='bright_black')
                rich.print(
                    "\tTo download files 1 to 9, the m*******t method, you type: 1,2,3,4,5,6,7,8,9",
                    style='bright_black')
                rich.print(
                    "\tTo download files 1 to 9, the cool-kid method, you type: 1-9",
                    style='bright_black')
                rich.print(
                    "\tTo download files 1 to 5 and files 8 to 10: 1-5,8-10",
                    style='bright_black')
                rich.print(
                    "\tTo download files 1, 4 and files 6 to 10: 1,4,6-10:",
                    style='bright_black')
                rich.print(
                    "\tTo download files 1, 4 and files 6 to 10, the crazy way, as the software doesn't care about order or duplicates: 10-6,1,4,6",
                    style='bright_black')
                rich.print("Exiting", style='bright_black')
                sys.exit(0)

            # parsing indexes
            index_to_download_raw = index_to_download_raw.replace(
                " ", "").split(",")
            index_to_download = []
            for i in index_to_download_raw:
                if "-" in i and i.count("-") == 1:
                    # spliting range
                    range_0, range_1 = i.split("-")
                    # test if is digit
                    if range_0.isdigit() == True and range_1.isdigit() == True:
                        # test if there's a zero
                        range_0 = int(range_0)
                        range_1 = int(range_1)

                        if range_0 < 1 or range_1 < 1:
                            rich.print(
                                "Error: invalid syntax, please only use non-zero positive integer numbers",
                                style='red on black')
                            sys.exit(1)
                        # test if digit 0 is bigger than digit 1
                        if range_0 < range_1:
                            # populate the index list
                            for a in range(range_0, range_1 + 1):
                                if a not in index_to_download:
                                    index_to_download.append(a)
                        elif range_0 > range_1:
                            for a in range(range_1, range_0 + 1):
                                if a not in index_to_download:
                                    index_to_download.append(a)
                        else:  # range_0 == range_1
                            if range_0 not in index_to_download:
                                index_to_download.append(range_0)
                    else:
                        rich.print(
                            "Error: syntax, please only use non-zero positive integer numbers",
                            style='red on black')
                        sys.exit(1)
                elif i.isdigit() == True:
                    if int(i) not in index_to_download:
                        index_to_download.append(int(i))
                else:
                    rich.print(
                        "Error: syntax, please only use non-zero positive integer numbers",
                        style='red on black')
                    sys.exit(1)

            # fixing indexes for python syntax and sorting the list
            index_to_download = sorted([int(x) - 1 for x in index_to_download])

            files_to_download = [maybe_download[i] for i in index_to_download]

            # if index_to_download_raw == "1":
            fillterm(
                "[green][SEARCH] you're going to download the following files[/green]"
            )

            process_search(files_to_download)

            # validation 2
            class Check_game_input_y_n(Validator):
                def validate(self, document):
                    text = document.text
                    if len(text) > 0:
                        if text.lower() not in ['y', 'n']:
                            # break
                            raise ValidationError(
                                message='Use "y" for yes and "n" for no',
                                cursor_position=0)
                    else:
                        raise ValidationError(
                            message='Enter something or press Ctrl+C to close.',
                            cursor_position=0)

            try:
                accept = prompt("Download files? [y/n]: ",
                                validator=Check_game_input_y_n())
                if accept.lower() != "y":
                    raise
            except KeyboardInterrupt:
                rich.print('Interrupted by user', style='bright_black')
                sys.exit(0)
            except:
                rich.print('Interrupted by user', style='bright_black')
                sys.exit(0)

        else:  # noconfirm download
            try:
                rich.print(
                    "ATTENTION! Since you used --noconfirm, you'll be downloading all the listed files, download will start in 4 seconds, you can use control+c to cancel now or at any time.",
                    style='green')
                sleep(4)
            except KeyboardInterrupt:
                rich.print('Interrupted by user', style='bright_black')
                sys.exit(0)
            except:
                rich.print('Interrupted by user', style='bright_black')
                sys.exit(0)

            files_to_download = maybe_download

####### skip all inputs to here in case of a resume :)
    """fully process game by game inside a single "for"
    this is useful to change the state for every game in the resume list
    proposed "Status" key in the dictionary will be as follow:
    
    0 - PKG not downloaded:
            this is already checked by the dl_file() fucntion!
            Also, this should be the default parameter?
    
    1 - PKG downloaded, but not extracted:
            the status will be updated to 1 after the download passes checksum
            a good place to do it could be around ### 1 HERE
    
    2 - PKG downloaded and extracted!
            the package should be removed from the resume dictionary when it reaches this status
            a good place to do it could be around ### 2 HERE
    """
    files_downloaded = []
    for i in files_to_download:
        # download file
        dl_result = dl_file(i, i['System'], DLFOLDER, WGET, limit_rate)

        if dl_result is True:
            rich.print("Download finished", style='dim green')
        elif dl_result is False:
            rich.print(
                'Unable to download .pkg file. Do you have a internet connection?',
                style='red on black')

        if dl_result == 'interrupted':
            # interrupted by user
            resume_dict = []
            for i in files_to_download:
                if i not in files_downloaded:
                    resume_dict.append(i)

            # run saving function
            # TODO: alert about closing with control + c to not save session
            # TODO don't let duplicate tags

            fillterm()

            # tag save validation
            class Check_tag_save(Validator):
                def validate(self, document):
                    text = document.text
                    if text.isalnum(
                    ) is False and text != "":  #only let alphanumeric
                        # break
                        raise ValidationError(
                            message=
                            'Only alphanumeric characters, without spaces, are supported for tag naming.',
                            cursor_position=0)

            try:
                tag = prompt(
                    "If you wanna save this download session to easily resume it latter, give this session a tag (you can use control+c to not save it or just leave blank to use a generated tag): ",
                    validator=Check_tag_save())
            except KeyboardInterrupt:
                rich.print('Interrupted by user', style='bright_black')
                sys.exit(0)

            if tag == "":
                tag = False

            # check if the current session already has a loaded UUID
            try:
                session_id = session['session_id']
                if tag == False:
                    tag = session['session_tag']
            except:
                # this means it's a new session!
                session_id = str(id_gen())
            download_save_state(resume_dict, DBFOLDER, id=session_id, tag=tag)

            sys.exit(0)

        downloaded_file_loc = f"{DLFOLDER}/PKG/{i['System']}/{i['Type']}/{i['PKG direct link'].split('/')[-1]}"

        # checksum
        # TODO: transform into function?
        if "SHA256" in i.keys():
            fillterm(
                f"[green on black][CHECKSUM][/green on black][green] Checking if downloaded file is valid"
            )
            if i["SHA256"] == "":
                rich.print('No checksum provided by NPS, skipping check',
                           style='dark_orange')
            else:
                sha256_dl = checksum_file(downloaded_file_loc)

                try:
                    sha256_exp = i["SHA256"]
                except:
                    sha256_exp = ""

                if sha256_dl != sha256_exp:
                    loc = f"{DLFOLDER}/PKG/{i['System']}/{i['Type']}/{i['PKG direct link'].split('/')[-1]}"
                    rich.print(
                        'Checksum not matching, pkg file is probably corrupted, delete it at your download folder and redownload the pkg',
                        style='red on black')
                    rich.print(
                        f'Corrupted file is located at [bright_black]{loc}[/bright_black]',
                        style='red on black')
                    break  # skip file
                else:
                    rich.print('Download is not corrupted :^)', style='green')

        ### 1 HERE

        dl_dile_loc = f"{DLFOLDER}/PKG/{i['System']}/{i['Type']}/{i['PKG direct link'].split('/')[-1]}"
        if PKG2ZIP != False and i['System'] != 'PS3':
            zrif = ""
            if args.compress_zip == True:
                dl_location = f"{DLFOLDER}/ZIP/{i['System']}/{i['Type'].capitalize()}"
            else:
                dl_location = f"{DLFOLDER}/Extracted"

            try:
                zrif = i['zRIF']
            except:
                pass

            # expose the exact directory were the pkg was extracted!
            extraction_folder = ""
            if i['System'] == "PSV":
                if i["Type"] in ["GAMES", "DEMOS"]:
                    extraction_folder = f"{DLFOLDER}/Extracted/app/{i['Title ID']}"
                if i["Type"] == "UPDATES":
                    extraction_folder = f"{DLFOLDER}/Extracted/patch/{i['Title ID']}"
                if i["Type"] == "DLCS":
                    extraction_folder = f"{DLFOLDER}/Extracted/addcont/{i['Title ID']}"
                if i["Type"] == "THEMES":
                    theme_folder_name = get_theme_folder_name(
                        f"{DLFOLDER}/Extracted/bgdl/t/")
                    extraction_folder = f"{DLFOLDER}/Extracted/bgdl/t/{theme_folder_name}/{i['Title ID']}"

            if i['System'] == "PSP":
                if i["Type"] == "GAMES":
                    if cso_factor == None and args.eboot == False:
                        extraction_folder = f"{DLFOLDER}/Extracted/pspemu/ISO/<i>game_name</i> [{i['Title ID']}].iso"
                    elif cso_factor in [str(x) for x in range(1, 10)]:
                        extraction_folder = f"{DLFOLDER}/Extracted/pspemu/ISO/<i>game_name</i> [{i['Title ID']}].cso"
                    elif args.eboot == True:
                        extraction_folder = f"{DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}"
                if i["Type"] == "DLCS":
                    extraction_folder = f"{DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}"
                if i["Type"] == "THEMES":
                    extraction_folder = f"{DLFOLDER}/Extracted/pspemu/PSP/THEME/<i>theme_name</i>.PTF"
                if i["Type"] == "UPDATES":
                    extraction_folder = f"{DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}"

            if i['System'] == "PSX":
                extraction_folder = f"{DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}"

            if i['System'] == "PSM":
                extraction_folder = f"{DLFOLDER}/Extracted/psm/{i['Title ID']}"

            # -x is default argument to not create .zip files
            if args.compress_zip == False:
                pkg2zip_args = ["-x"]
            else:
                pkg2zip_args = []

            if cso_factor != None and i["Type"] == "GAMES" and i[
                    'System'] == "PSP":
                pkg2zip_args.append("-c" + cso_factor)
            elif cso_factor != None and i["Type"] != "UPDATES" and i[
                    'System'] != "PSP":
                rich.print(
                    f"cso is only supported for PSP games, since you're extracting a {i['System']} {i['Type'][:-1].lower()} the compression will be skipped",
                    style='dark_orange')

            if args.eboot == True and i["Type"] == "GAMES" and i[
                    'System'] == "PSP":
                pkg2zip_args.append("-p")
            # append more commands here if needed!

            if args.compress_zip == True:
                extraction_folder = dl_location

            fillterm(
                f"[green on black][PKG2ZIP][/green on black][green] Attempting extraction"
            )

            if i['System'] == "PSV" and zrif not in ["", "MISSING", None]:
                delete = run_pkg2zip(dl_dile_loc, dl_location, PKG2ZIP,
                                     pkg2zip_args, extraction_folder, i, zrif)
            else:
                delete = run_pkg2zip(dl_dile_loc, dl_location, PKG2ZIP,
                                     pkg2zip_args, extraction_folder, i)

            #testing if extraction was completion and delete file if needed
            if delete == True and keepkg == False:
                # delete file
                try:
                    os.remove(dl_dile_loc)
                    rich.print("The compressed .pkg was deleted",
                               style='green')
                except:
                    rich.print(
                        f"Unable to delete file, you may want to it manually at [bright_black]{dl_dile_loc}[/bright_black]",
                        style='red on black')

            ### 2 HERE
            files_downloaded.append(i)
        else:
            if i['System'] != 'PS3':
                fillterm(
                    f"[green on black][PKG2ZIP][/green on black][green] Attempting extraction"
                )
                rich.print(
                    "Skipping extraction since there's no pkg2zip binary in your system",
                    style='red on black')
            else:
                # move if it's a PS3 pkg
                # move to ~/PS3/packages
                # rap files go to ~/PS3/exdata
                fillterm(
                    f"[green on black][MOVE][/green on black][green] Attempting to move files"
                )

                # get downloaded file name
                pkg_location = f"{DLFOLDER}/PKG/{i['System']}/{i['Type']}/{i['PKG direct link'].split('/')[-1]}"

                # new name
                pkg_new_location = f"{DLFOLDER}/PS3/{i['Type']}/packages/{i['Name']} ({i['Region']}) - {i['Content ID']}.pkg"

                delete = False
                try:
                    # touch new folder
                    create_folder(os.path.dirname(pkg_new_location))
                    # copy file and set delte as True if it's sucessfull
                    copyfile(pkg_location, pkg_new_location)
                    rich.print(
                        f"PS3 pkg file moved to [bright_black]{pkg_new_location}[/bright_black]",
                        style='green')
                    delete = True
                except:
                    rich.print(f"Unable to move PS3 pkg file",
                               style='red on black')

                #testing if extraction was completion and delete file if needed
                if delete == True and keepkg == False:
                    # delete file
                    try:
                        os.remove(dl_dile_loc)
                        rich.print(f"The compressed .pkg was deleted",
                                   style='green')
                    except:
                        rich.print(
                            f"Unable to delete file, you may want to it manually at [bright_black]{dl_dile_loc}[/bright_black]",
                            style='red on black')

                # download rap file
                fillterm(
                    f"[green on black][RAP][/green on black][green] Trying to download RAP file"
                )

                if i['RAP'] == "NOT REQUIRED":
                    rich.print(f"RAP files aren't required for this game :^)",
                               style='green')
                elif i['RAP'] == "MISSING" or i['RAP'] == "":
                    rich.print(
                        f"Unfortunatelly there are no RAP files available for this game on Nopaystation",
                        style='dark_orange')
                elif i['RAP'] == "UNLOCK/LICENSE BY DLC":
                    rich.print(f"This pkg is unlocked by a DLC",
                               style='dark_orange')

                else:
                    rap_url = f"https://nopaystation.com/tools/rap2file/{i['Content ID']}/{i['RAP']}"
                    rap_folder = f"{DLFOLDER}/PS3/{i['Type']}/exdata/{i['Content ID']}.rap"
                    # printft(HTML("<green>[RAP] downloaing RAP file</green>"))
                    rap_state = get_rap(i, WGET, rap_folder, rap_url)

                    if rap_state:
                        rich.print(
                            f"PS3 RAP file downloaded to: [bright_black]{rap_folder}[/bright_black]",
                            style='green')
                    else:
                        rich.print(
                            'Unable to download .rap file. Do you have a internet connection?',
                            style='red on black')

            ### 2 HERE
            files_downloaded.append(i)

    fillterm()
    if get_xmas():
        rich.print(f"Done! [red]Happy Holidays!!![/red] 🎄✨",
                   style='green')
    else:
        rich.print(f"Done!", style='green')
Exemplo n.º 8
0
Arquivo: cli.py Projeto: Doctus/pynps
def cli_main(maindir=""):
    """implement pynps cli interface"""

    if get_system() == 'Linux':
        CONFIGFOLDER = f"{os.getenv('HOME')}/.config/pyNPS"
        config_file = f"{CONFIGFOLDER}/settings.ini"
    elif get_system() == 'Windows':
        CONFIGFOLDER = f"{maindir}/pynps_config/"
        config_file = f"{CONFIGFOLDER}/settings.ini"
    else:
        CONFIGFOLDER = ""
        config_file = ""

    # create conf file
    if os.path.isfile(config_file) == False:
        create_config(config_file, CONFIGFOLDER)
        create_folder(CONFIGFOLDER+"/lib/")

    # read conf file
    config = configparser.ConfigParser()
    config.read(config_file)

    # test sections
    if sorted(config.sections()) != sorted(['pyNPS', 'PSV_Links', 'PSP_Links', 'PSX_Links', 'PSM_Links', 'BinaryLocations']):
        printft(HTML("<red>[ERROR] config file: missing sections</red>"))
        print("You need the following sections in your config file: 'PSV_Links', 'PSP_Links', 'PSX_Links', 'PSM_Links', 'BinaryLocations'")
        sys.exit(1)
    if sorted(list(config["PSV_Links"])) != sorted(['games', 'dlcs', 'themes', 'updates', 'demos']):
        printft(HTML("<red>[ERROR] config file: missing options in the PSV_Links section</red>"))
        print("You need the following options in your PSV_Links section: 'games', 'dlcs', 'themes', 'updates', 'demos'")
        sys.exit(1)
    if sorted(list(config["PSP_Links"])) != sorted(['games', 'dlcs', 'themes', 'updates']):
        printft(HTML("<red>[ERROR] config file: missing options in the PSP_Links section</red>"))
        print("You need the following options in your PSP_Links section: 'games', 'dlcs', 'themes', 'updates'")
        sys.exit(1)
    if sorted(list(config["PSX_Links"])) != sorted(['games']):
        printft(HTML("<red>[ERROR] config file: missing options in the PSX_Links section</red>"))
        print("You need the following options in your PSX_Links section: 'games'")
        sys.exit(1)
    if sorted(list(config["PSM_Links"])) != sorted(['games']):
        printft(HTML("<red>[ERROR] config file: missing options in the PSM_Links section</red>"))
        print("You need the following options in your PSM_Links section: 'games'")
        sys.exit(1)

    # making vars
    DBFOLDER = fix_folder_syntax(config['pyNPS']['databasefolder'], maindir)
    DLFOLDER = fix_folder_syntax(config['pyNPS']['downloadfolder'], maindir)
    PKG2ZIP = fix_folder_syntax(config['BinaryLocations']['pkg2zip_location'], maindir)
    WGET = fix_folder_syntax(config['BinaryLocations']['wget_location'], maindir)

    # tests existence of pkg2zip
    PKG2ZIP = check_pkg2zip(PKG2ZIP, CONFIGFOLDER)
    if PKG2ZIP == False:
        printft(HTML("<orange>[PKG2ZIP] you don't have a valid pkg2zip installation or binary in your system, extraction will be skipped</orange>"))
        # sys.exit(1)

    # tests existence of wget
    WGET = check_wget(WGET, CONFIGFOLDER)
    if WGET == False:
        printft(HTML("<red>[ERROR] you don't have a valid wget installation or binary in your system, this program can't work without it</red>"))
        sys.exit(1)

    # makin dicts for links
    database_psv_links = {}
    for key in config["PSV_Links"]:
        database_psv_links[key] = config["PSV_Links"][key]

    database_psp_links = {}
    for key in config["PSP_Links"]:
        database_psp_links[key] = config["PSP_Links"][key]

    database_psx_links = {}
    for key in config["PSX_Links"]:
        database_psx_links[key] = config["PSX_Links"][key]

    database_psm_links = {}
    for key in config["PSM_Links"]:
        database_psm_links[key] = config["PSM_Links"][key]

    # create args
    args, parser = create_args()

    limit_rate = args.limit_rate
    keepkg = args.keepkg
    system = args.console
    cso_factor = args.compress_cso
    
    if args.resume_session:
        # in this case args.search will be considered a tag to fast resume a session

        input_tag = args.search
        if input_tag is not None:
            if input_tag.isalnum() is False:
                printft(HTML("<orange>[DOWNLOAD] Tags can only be alphanumeric without spaces</orange>"))
                input_tag = None
           
        ##load download db
        with SqliteDict(f"{DBFOLDER}/downloads.db", autocommit=False) as database:
            try:
                db = database['resumes']
                if len(db) == 0:
                    raise
            except:
                printft(HTML("<orange>[DOWNLOAD] There are no saved download sessions to resume</orange>"))
                sys.exit(0)

        checker = next((item for item in db if item['session_tag'] == input_tag), None)
        
        ## if tag is prvided and there's a match in the db
        if input_tag is not None and checker is not None:
            # has something in db
            session = checker

        elif checker is None or input_tag is None:
            if input_tag is not None:
                printft(HTML("<orange>[DOWNLOAD] There's no such tag in download sessions</orange>"))

                # validation resume input
                class Check_resume_input_y_n(Validator):
                    def validate(self, document):
                        text = document.text
                        if len(text) > 0:
                            if text.lower() not in ['y', 'n']:
                                # break
                                raise ValidationError(message='Use "y" for yes and "n" for no',
                                                    cursor_position=0)
                        else:
                            raise ValidationError(message='Enter something or press Ctrl+C to close.',
                                                cursor_position=0)

                try:
                    yn_check = prompt("Wanna see all currently download sessions? [y/n]: ",
                                    validator=Check_resume_input_y_n())
                    if yn_check.lower() != "y":
                        raise
                except KeyboardInterrupt:
                    printft(HTML("<grey>Interrupted by user</grey>"))
                    sys.exit(0)
                except:
                    printft(HTML("<grey>Interrupted by user</grey>"))
                    sys.exit(0)
            
            printft(HTML("<grey>%s</grey>") %fill_term())
            
            p_db = []
            for index_file, i in enumerate(db):
                i["Index"] = index_file + 1
                p_db.append(i)

            
            process_resumes(p_db)

            # validating input
            class Check_resume_input(Validator):
                def validate(self, document):
                    text = document.text
                    if len(text) > 0:
                        if text.isdigit():

                            # test if number being typed is bigger than the biggest number from game list
                            num_p = len(db)
                            if num_p == 1 and text != "1":
                                raise ValidationError(message="Your only option is to type 1.",
                                                    cursor_position=0)
                            if int(text) > num_p:
                                raise ValidationError(
                                    message=f"There are no entries past {num_p}.")
                        else:
                            raise ValidationError(message="Please only use numbers",
                                                    cursor_position=0)
                    else:
                        raise ValidationError(message='Enter something or press Ctrl+C to close. Press "h" for help.',
                                            cursor_position=0)


            try:
                session_index = prompt("Enter the number for the download session you want to resume: ", 
                                                validator=Check_resume_input())
            except KeyboardInterrupt:
                printft(HTML("<grey>Interrupted by user</grey>"))
                sys.exit(0)
            except:
                printft(HTML("<grey>Interrupted by user</grey>"))
                sys.exit(0)

            session = db[int(session_index) - 1]

        files_to_download = session["session_dict"]

    else:

        what_to_dl = {"games": args.games, "dlcs": args.dlcs, "themes": args.themes,
                    "updates": args.updates, "demos": args.demos}

        if set(what_to_dl.values()) == set([False]):
            for i in what_to_dl:
                what_to_dl[i] = True

        if args.update == True:
            if [args.region, args.search, args.eboot, args.compress_cso] != [None, None, False, None]:
                printft(HTML("<red>[UPDATEDB] you can't search while updating the database</red>"))
                sys.exit(1)

            printft(HTML("<grey>%s</grey>") %fill_term())
            what_to_up = [x for x in what_to_dl if what_to_dl[x] == True]

            for i in system:
                printft(HTML("<green>[UPDATEDB] %s:</green>") %variables.FULL_SYSTEM_NAME[i])
                if i == "PSV":
                    db = database_psv_links
                elif i == "PSP":
                    db = database_psp_links
                elif i == "PSX":
                    db = database_psx_links
                elif i == "PSM":
                    db = database_psm_links
                
                # parsing supported
                what_to_up_parsed = [x for x in what_to_up if x in db.keys()]
                
                if len(what_to_up_parsed) > 0:
                    updatedb(db, i, DBFOLDER, WGET, what_to_up_parsed)
                else:
                    printft(HTML("<blue>Nothing to do!</blue>"))

            printft(HTML("<blue>Done!</blue>"))
            sys.exit(0)

        elif args.update == False and args.search is None:
            printft(HTML("<red>[SEARCH] No search term provided, you need to search for something</red>"))
            parser.print_help()
            sys.exit(1)

        # checking for database's existense:
        if os.path.isfile(f"{DBFOLDER}/pynps.db") is False:
            printft(HTML("<red>[UPDATEDB] theres no database in your system, please update your database and try again</red>"))
            sys.exit(1)


        # check region
        if args.region == None:
            reg = ["usa", "eur", "jap", "asia", "int"]
        else:
            reg = args.region

        # maybe_download = []
        maybe_download = search_db(system, what_to_dl, args.search, reg, DBFOLDER)

        # test if the result isn't empty
        if len(maybe_download) == 0:
            printft(HTML("<orange>[SEARCH] No results found, try searching for something else or updating your database</orange>"))
            sys.exit(0)

        # adding indexes to maybe_download
        for i in range(0, len(maybe_download)):
            # maybe_download[i]["Index"] = str(i)
            maybe_download[i]["Index"] = str(i + 1)

        # print possible mathes to the user
        if len(maybe_download) > 1:
            if args.print is True:
                for i in maybe_download:
                    print(f"{i['Index']} {i['System']} | {i['Title ID']} | {variables.REGION_DICT[i['Region']]} | {i['Type']} | {i['Name']} | [{file_size(i['File Size'])}]".encode("utf-8"))
                sys.exit(0)
            printft(HTML("<grey>%s</grey>") %fill_term())
            printft(HTML("<green>[SEARCH] here are the matches:</green>"))
            process_search(maybe_download)
            
        # validating input
        class Check_game_input(Validator):
            def validate(self, document):
                text = document.text
                if len(text) > 0:
                    # test if number being typed is bigger than the biggest number from game list
                    num_p = len(maybe_download)
                    if num_p == 1 and text != "1":
                        raise ValidationError(message="Your only option is to type 1.",
                                            cursor_position=0)
                    text_processed = text.replace(
                        "-", " ").replace(",", " ").split(" ")
                    last_texttext_processed = [
                        x for x in text_processed if x != ""]
                    last_text = text_processed[-1]

                    if "0" in text_processed:
                        raise ValidationError(message='Zero is an invalid entry.')

                    if last_text.isdigit():
                        last_text = int(last_text)
                        if last_text > num_p:
                            raise ValidationError(
                                message=f"There are no entries past {num_p}.")

                    if text.startswith("-") or text.startswith(","):
                        # break
                        raise ValidationError(message='Start the input with a number. Press "h" for help.',
                                            cursor_position=0)

                    if "--" in text or ",," in text or ",-" in text or "-," in text:
                        raise ValidationError(
                            message='Use only one symbol to separate numbers. Press "h" for help.')

                    text = text.replace("-", "").replace(",", "")
                    if text.isdigit() is False and text != "h":
                        raise ValidationError(
                            message='Do not use leters. Press "h" for help.')
                else:
                    raise ValidationError(message='Enter something or press Ctrl+C to close. Press "h" for help.',
                                        cursor_position=0)

        if len(maybe_download) > 1:
            try:
                index_to_download_raw = prompt("Enter the number for what you want to download, you can enter multiple numbers using commas: ", 
                                                validator=Check_game_input())
            except KeyboardInterrupt:
                printft(HTML("<grey>Interrupted by user</grey>"))
                sys.exit(0)
            except:
                printft(HTML("<grey>Interrupted by user</grey>"))
                sys.exit(0)
        else:
            index_to_download_raw = "1"

        # provides help
        if index_to_download_raw.lower() == "h":
            printft(HTML("<grey>\tSuppose you have 10 files to select from:</grey>"))
            printft(HTML("<grey>\tTo download file 2, you type: 2</grey>"))
            printft(HTML("<grey>\tTo download files 1 to 9, the m*******t method, you type: 1,2,3,4,5,6,7,8,9</grey>"))
            printft(HTML("<grey>\tTo download files 1 to 9, the cool-kid method, you type: 1-9</grey>"))
            printft(HTML("<grey>\tTo download files 1 to 5 and files 8 to 10: 1-5,8-10</grey>"))
            printft(HTML("<grey>\tTo download files 1, 4 and files 6 to 10: 1,4,6-10</grey>"))
            printft(HTML("<grey>\tTo download files 1, 4 and files 6 to 10, the crazy way, as the software doesn't care about order or duplicates: 10-6,1,4,6</grey>"))
            printft(HTML("<grey>Exiting</grey>"))
            sys.exit(0)

        # parsing indexes
        index_to_download_raw = index_to_download_raw.replace(" ", "").split(",")
        index_to_download = []
        for i in index_to_download_raw:
            if "-" in i and i.count("-") == 1:
                # spliting range
                range_0, range_1 = i.split("-")
                # test if is digit
                if range_0.isdigit() == True and range_1.isdigit() == True:
                    # test if there's a zero
                    range_0 = int(range_0)
                    range_1 = int(range_1)

                    if range_0 < 1 or range_1 < 1:
                        print("ERROR: Invalid syntax, please only use non-zero positive integer numbers")
                        sys.exit(1)
                    # test if digit 0 is bigger than digit 1
                    if range_0 < range_1:
                        # populate the index list
                        for a in range(range_0, range_1+1):
                            if a not in index_to_download:
                                index_to_download.append(a)
                    elif range_0 > range_1:
                        for a in range(range_1, range_0+1):
                            if a not in index_to_download:
                                index_to_download.append(a)
                    else:  # range_0 == range_1
                        if range_0 not in index_to_download:
                            index_to_download.append(range_0)
                else:
                    print("ERROR: Invalid syntax, please only use non-zero positive integer numbers")
                    sys.exit(1)
            elif i.isdigit() == True:
                if int(i) not in index_to_download:
                    index_to_download.append(int(i))
            else:
                print("ERROR: Invalid syntax, please only use non-zero positive integer numbers")
                sys.exit(1)

        # fixing indexes for python syntax and sorting the list
        index_to_download = sorted([int(x)-1 for x in index_to_download])

        files_to_download = [maybe_download[i] for i in index_to_download]

        # if index_to_download_raw == "1":
        printft(HTML("<grey>%s</grey>") %fill_term())
        printft(HTML("<green>[SEARCH] you're going to download the following files:</green>"))


        process_search(files_to_download)


        # validation 2
        class Check_game_input_y_n(Validator):
            def validate(self, document):
                text = document.text
                if len(text) > 0:
                    if text.lower() not in ['y', 'n']:
                        # break
                        raise ValidationError(message='Use "y" for yes and "n" for no',
                                            cursor_position=0)
                else:
                    raise ValidationError(message='Enter something or press Ctrl+C to close.',
                                        cursor_position=0)

        try:
            accept = prompt("Download files? [y/n]: ",
                            validator=Check_game_input_y_n())
            if accept.lower() != "y":
                raise
        except KeyboardInterrupt:
            printft(HTML("<grey>Interrupted by user</grey>"))
            sys.exit(0)
        except:
            printft(HTML("<grey>Interrupted by user</grey>"))
            sys.exit(0)

####### skip all inputs to here in case of a resume :)
    """fully process game by game inside a single "for"
    # this is useful to change the state for every game in the resume list
    # proposed "Status" key in the dictionary will be as follow:
    
    # 0 - PKG not downloaded:
    #         this is already checked by the dl_file() fucntion!
    #         Also, this should be the default parameter?
    
    # 1 - PKG downloaded, but not extracted:
    #         the status will be updated to 1 after the download passes checksum
    #         a good place to do it could be around ### 1 HERE
    
    # 2 - PKG downloaded and extracted!
    #         the package should be removed from the resume dictionary when it reaches this status
    #         a good place to do it could be around ### 2 HERE

    'files_downloaded' starts with no itens
    'files_to_download' starts with all itens to be downloaded

    for i in files_to_download:
        downloads i

        if dl_results is False:
            do processing for resuming download latter
        
        save download locationg into 'downloaded_file_loc' var

        call pkg_checksum() function (WIP) into 'chekcsum' var

        if checksum is True:
            if PKG2ZIP == False:
                append i to 'files_downloaded' list
            else:
                proceed to extract pkg with pkg2zip

                if extraction is ok:
                    delete pkg file if needed
                    append i to 'files_downloaded' list
                    
                remove i from list in the database!!! 
        else:
            skip file
    """
    files_downloaded = []
    for i in files_to_download:
        # download file
        dl_result = dl_file(i,  i['System'], DLFOLDER, WGET, limit_rate)

        if dl_result is False:
            # interrupted by user
            resume_dict = []
            for i in files_to_download:
                if i not in files_downloaded:
                    resume_dict.append(i)
            
            # run saving function
            # TODO: alert about closing with control + c to not save session
            # TODO don't let duplicate tags
            
            printft(HTML("<grey>%s</grey>") %fill_term())
            # tag save validation
            class Check_tag_save(Validator):
                def validate(self, document):
                    text = document.text
                    if text.isalnum() is False and text != "": #only let alphanumeric
                        # break
                        raise ValidationError(message='Only alphanumeric characters, without spaces, are supported for tag naming.',
                                            cursor_position=0)

            try:
                tag = prompt("If you wanna save this download session to easily resume it latter, give this session a tag (you can use control+c to not save it or just leave blank to use a generated tag): ",
                                validator=Check_tag_save())
            except KeyboardInterrupt:
                printft(HTML("<grey>Interrupted by user</grey>"))
                sys.exit(0)
            
            if tag == "":
                tag = False

            # check if the current session already has a loaded UUID
            try:
                session_id = session['session_id']
                if tag == False:
                    tag = session['session_tag']
            except:
                # this means it's a new session!
                session_id = str(id_gen())
            download_save_state(resume_dict, DBFOLDER, id=session_id,  tag=tag)

            sys.exit(0)

        downloaded_file_loc = f"{DLFOLDER}/PKG/{i['System']}/{i['Type']}/{i['PKG direct link'].split('/')[-1]}"

        # checksum
        # TODO: transform into function?
        if "SHA256" in i.keys():
            printft(HTML("<grey>%s</grey>") %fill_term())
            if i["SHA256"] == "":
                printft(HTML("<orange>[CHECKSUM] No checksum provided by NPS, skipping check</orange>"))
            else:

                sha256_dl = checksum_file(downloaded_file_loc)

                try:
                    sha256_exp = i["SHA256"]
                except:
                    sha256_exp = ""

                if sha256_dl != sha256_exp:
                    loc = f"{DLFOLDER}/PKG/{i['System']}/{i['Type']}/{i['PKG direct link'].split('/')[-1]}"
                    printft(HTML("<red>[CHECKSUM] checksum not matching, pkg file is probably corrupted, delete it at your download folder and redownload the pkg</red>"))
                    printft(HTML("<red>[CHECKSUM] corrupted file location: %s</red>") %loc)
                    break # skip file
                else:
                    printft(HTML("<green>[CHECKSUM] downloaded is not corrupted!</green>"))
        
        ### 1 HERE
        

        printft(HTML("<grey>%s</grey>") %fill_term())

        if PKG2ZIP != False:
            zrif = ""
            dl_dile_loc = f"{DLFOLDER}/PKG/{i['System']}/{i['Type']}/{i['PKG direct link'].split('/')[-1]}"
            dl_location = f"{DLFOLDER}/Extracted"

            try:
                zrif = i['zRIF']
            except:
                pass

            # expose the exact directory were the pkg was extracted!
            extraction_folder = ""
            if i['System'] == "PSV":
                if i["Type"] in ["GAMES", "DEMOS"]:
                    extraction_folder = f"{DLFOLDER}/Extracted/app/{i['Title ID']}"
                    # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/app/{i['Title ID']}</green>"))
                if i["Type"] == "UPDATES":
                    extraction_folder = f"{DLFOLDER}/Extracted/patch/{i['Title ID']}"
                    # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/patch/{i['Title ID']}</green>"))
                if i["Type"] == "DLCS":
                    extraction_folder = f"{DLFOLDER}/Extracted/addcont/{i['Title ID']}"
                    # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/addcont/{i['Title ID']}</green>"))
                if i["Type"] == "THEMES":
                    theme_folder_name = get_theme_folder_name(f"{DLFOLDER}/Extracted/bgdl/t/")
                    extraction_folder = f"{DLFOLDER}/Extracted/bgdl/t/{theme_folder_name}/{i['Title ID']}"
                    # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/bgdl/t/{theme_folder_name}/{i['Title ID']}</green>"))

            if i['System'] == "PSP":
                if i["Type"] == "GAMES":
                    if cso_factor == None and args.eboot == False:
                        extraction_folder = f"{DLFOLDER}/Extracted/pspemu/ISO/<i>game_name</i> [{i['Title ID']}].iso"
                        # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/pspemu/ISO/<i>game_name</i> [{i['Title ID']}].iso</green>"))
                    elif cso_factor in [str(x) for x in range(1, 10)]:
                        extraction_folder = f"{DLFOLDER}/Extracted/pspemu/ISO/<i>game_name</i> [{i['Title ID']}].cso"
                        # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/pspemu/ISO/<i>game_name</i> [{i['Title ID']}].cso</green>"))
                    elif args.eboot == True:
                        extraction_folder = f"{DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}"
                        # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}</green>"))
                if i["Type"] == "DLCS":
                    extraction_folder = f"{DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}"
                    # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}</green>"))
                if i["Type"] == "THEMES":
                    extraction_folder = f"{DLFOLDER}/Extracted/pspemu/PSP/THEME/<i>theme_name</i>.PTF"
                    # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/pspemu/PSP/THEME/<i>theme_name</i>.PTF</green>"))
                if i["Type"] == "UPDATES":
                    extraction_folder = f"{DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}"
                    # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}</green>"))

            if i['System'] == "PSX":
                extraction_folder = f"{DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}"
                # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/pspemu/PSP/GAME/{i['Title ID']}</green>"))

            if i['System'] == "PSM":
                extraction_folder = f"{DLFOLDER}/Extracted/psm/{i['Title ID']}"
                # printft(HTML(f"<green>[EXTRACTION] {i['Name']} ➔ {DLFOLDER}/Extracted/psm/{i['Title ID']}</green>"))

            # -x is default argument to not create .zip files
            pkg2zip_args = ["-x"]
            if cso_factor != None and i["Type"] == "GAMES" and i['System'] == "PSP":
                pkg2zip_args.append("-c"+cso_factor)
            elif cso_factor != None and i["Type"] != "UPDATES" and i['System'] != "PSP":
                printft(HTML("<orange>[EXTRACTION] cso is only supported for PSP games, since you're extracting a %s %s the compression will be skipped</orange>") %(i['System'], i['Type'][:-1].lower()))

            if args.eboot == True and i["Type"] == "GAMES" and i['System'] == "PSP":
                pkg2zip_args.append("-p")
            # append more commands here if needed!

            printft(HTML("<green>[PKG2ZIP] Attempting to extract [%s]%s</green>") %(i['Title ID'], i['Name']))

            if i['System'] == "PSV" and zrif not in ["", "MISSING", None]:
                delete = run_pkg2zip(dl_dile_loc, dl_location, PKG2ZIP, pkg2zip_args, extraction_folder, zrif)
            else:
                delete = run_pkg2zip(dl_dile_loc, dl_location, PKG2ZIP, pkg2zip_args, extraction_folder)

            #testing if extraction was completion and delete file if needed
            
            if delete == True and keepkg == False:
                # delete file
                printft(HTML("<green>[EXTRACTION] Attempting to delete .pkg file</green>"))
                try:
                    os.remove(dl_dile_loc)
                    printft(HTML("<green>[EXTRACTION] Success, the compressed .pkg was deleted</green>"))
                except:
                    printft(HTML("<red>[EXTRACTION] Unable to delete, you may want to it manually at: </red><grey>%s</grey>") %dl_dile_loc)

            ### 2 HERE
            files_downloaded.append(i)
        else:
            printft(HTML("<orange>[EXTRACTION] skipping extraction since there's no pkg2zip binary in your system</orange>"))
            
            ### 2 HERE
            files_downloaded.append(i)

    printft(HTML("<grey>%s</grey>") %fill_term())
    printft(HTML("<blue>Done!</blue>"))