Compare commits

...

7 commits

3 changed files with 257 additions and 47 deletions

207
mc-get.py
View file

@ -6,13 +6,16 @@ import os
import mcgetdb
from colorama import Fore, Style
db = mcgetdb.McGetDB(mcfs.mc_dir)
class HashError(ValueError):
"""Incorrect hash"""
def validate():
raise NotImplementedError
def download_cache(url: str, filename: str, size: int):
def download_cache(url: str, filename: str, size: int, hash: str):
cache_file_path = os.path.join(mcfs.cache_dir, filename)
if not mcfs.is_path_exist(mcfs.cache_dir):
os.mkdir(mcfs.cache_dir)
@ -20,7 +23,9 @@ def download_cache(url: str, filename: str, size: int):
api.download(url, size, cache_file_path)
else:
print(f"{filename} is in cache.")
return filename
if not mcfs.check_file_hash(cache_file_path, hash):
os.remove(cache_file_path)
raise HashError(f"Incorrect hash for {filename}")
def modpack_install(filename: str):
@ -30,20 +35,30 @@ def modpack_install(filename: str):
path = file["path"].split("/")
downloads = file["downloads"]
print(file["path"])
download_cache(downloads[0], path[1], file["fileSize"])
download_cache(downloads[0], path[1], file["fileSize"], file["hashes"]["sha512"])
mcfs.install(path[1], path[0])
print(f"{Fore.YELLOW}Overriding...{Style.RESET_ALL}")
mcfs.install_modpacks_override(filename)
def install(projects: list, version, loader):
def install(projects: list, mc_ver, loader):
to_install = []
dependencies_to_install = []
not_found = []
unavailable = []
projects_ids = []
already_installed = []
def get_project_info_from_db(slug):
_db = mcgetdb.McGetDB(mcfs.mc_dir)
return _db.select_mod(slug) + _db.select_shader(slug) + _db.select_resourcepack(slug) + _db.select_modpack(slug)
def check_project(slug):
if __name__ == "__main__":
mod_info = get_project_info_from_db(slug)
if mod_info:
already_installed.append(mod_info[0])
return
id = api.check_project(project=slug)
if id:
projects_ids.append(id.id)
@ -74,15 +89,24 @@ def install(projects: list, version, loader):
for project in projects_ids:
project_data = api.project(project=project)
versions = api.get_versions(project=project, loaders=f'["{loader}"]',
game_versions=f'["{mc_ver}"]')
match project_data.project_type:
case "resourcepack":
versions = api.get_versions(project=project, game_versions=f'["{mc_ver}"]')
case "mod" | "modpack":
versions = api.get_versions(project=project, loaders=f'["{loader}"]', game_versions=f'["{mc_ver}"]')
case "shader": # TODO: Реализовать поддержку загрузчиков шейдеров
versions = api.get_versions(project=project, game_versions=f'["{mc_ver}"]')
case _:
raise NotImplementedError
if versions:
to_install.append((project_data, versions[0]))
dependency_solver(versions[0])
else:
unavailable.append(project_data)
print("To install:", *[project.title + " " + version.version_number for project, version in to_install], sep="\n\t")
if to_install:
print("To install:", *[project.title + " " + version.version_number for project, version in to_install],
sep="\n\t")
if dependencies_to_install:
print("With dependencies:",
*[project.title + " " + version.version_number for project, version in dependencies_to_install],
@ -93,15 +117,33 @@ def install(projects: list, version, loader):
if unavailable:
print("Cannot be installed:", *[project.title for project in unavailable], sep="\n\t")
choose = input("Continue? [y/n]")
if already_installed:
print("Already installed:", *[name + " " + version for _, name, _, version, _ in already_installed], sep="\n\t")
all_to_install = to_install + dependencies_to_install
if not all_to_install:
return
choose = input("Continue? [y/n] ")
if choose.strip().lower() in ["n", "no"]:
print("Canceled.")
return
for project, version in to_install + dependencies_to_install:
failed_to_install = []
for project, version in all_to_install:
file = type("mc_file", (object,), version.files[0])
filename = project.slug + ".jar"
download_cache(file.url, filename, file.size)
filename = file.filename
hash = file.hashes["sha512"]
try:
download_cache(file.url, filename, file.size, hash)
except HashError:
print(f"Failed to install {project.title} ({project.slug}) [{version.version_number}] due to an incorrect "
f"hash")
failed_to_install.append((project, version))
continue
match project.project_type:
case "resourcepack":
subdir = "resourcepacks"
@ -111,11 +153,31 @@ def install(projects: list, version, loader):
subdir = "shaderpacks"
case "modpack":
modpack_install(filename)
continue
subdir = "modpacks"
case _:
raise NotImplementedError
if __name__ == "__main__":
db = mcgetdb.McGetDB(mcfs.mc_dir)
match project.project_type:
case "resourcepack":
db.add_resourcepack(slug=project.slug, proj_name=project.title, filename=filename,
version=version.version_number, hash=hash)
case "mod":
db.add_mod(slug=project.slug, proj_name=project.title, filename=filename,
version=version.version_number, hash=hash)
case "shader":
db.add_shader(slug=project.slug, proj_name=project.title, filename=filename,
version=version.version_number, hash=hash)
case "modpack":
db.add_modpack(slug=project.slug, proj_name=project.title, filename=filename,
version=version.version_number, hash=hash)
case _:
raise NotImplementedError
mcfs.install(filename, subdir)
if failed_to_install:
print("Failed to install:",
*[project.title + " " + version.version_number for project, version in failed_to_install], sep="\n\t")
def search(query: list):
@ -142,39 +204,101 @@ def clean():
print("Nothing to clear.")
def remove(projects): # TODO: 1. Проверка зависимостей? 2. Модпаки
if __name__ == "__main__":
db = mcgetdb.McGetDB(mcfs.mc_dir)
mods_to_remove = []
resources_to_remove = []
shaders_to_remove = []
modpacks_to_remove = []
for project in projects:
for res in db.select_mod(project):
mods_to_remove.append(res)
for res in db.select_resourcepack(project):
resources_to_remove.append(res)
for res in db.select_shader(project):
shaders_to_remove.append(res)
for res in db.select_modpack(project):
modpacks_to_remove.append(res)
for slug, title, filename, version, _ in mods_to_remove:
local_path = os.path.join("mods", filename)
mcfs.remove(local_path)
db.remove_mod(slug)
print(f"Mod {title} v.{version} was removed.")
for _, title, filename, version, _ in resources_to_remove:
local_path = os.path.join("resourcepacks", filename)
mcfs.remove(local_path)
db.remove_resourcepack(slug)
print(f"Resource pack {title} v.{version} was removed.")
for _, title, filename, version, _ in shaders_to_remove:
local_path = os.path.join("shaderpacks", filename)
mcfs.remove(local_path)
db.remove_shader(slug)
print(f"Shader pack {title} v.{version} was removed.")
for _, title, filename, version, _ in modpacks_to_remove:
raise NotImplementedError
# local_path = os.path.join("modpacks", filename)
# mcfs.remove(local_path)
# db.remove_modpack(slug)
# print(f"Mod pack {title} v.{version} was removed.")
else:
raise NotImplementedError("Not available in module mode")
if __name__ == "__main__":
def exit():
import sys
sys.exit("MC installation not found. If the program is not installed in the default location, "
"then specify the path to the installation through the MC_DIR environment variable.")
if not mcfs.is_path_exist(mcfs.mc_dir):
exit()
db = mcgetdb.McGetDB(mcfs.mc_dir)
def __select_version(versions):
if len(versions) > 0:
print("Installed MC versions: ")
for id, version in enumerate(versions):
print(id + 1, version.version_number + (f" with {version.modloader}" if version.is_modified else ""),
sep=": ")
if len(versions) > 1:
id_to_use = -1
while id_to_use not in range(1, len(versions)):
id_input = input(f"Select MC version to use [1-{len(versions)}]: ")
if not id_input.isnumeric():
continue
id_to_use = int(id_input)
else:
id_to_use = 1
selected_version = versions[id_to_use - 1]
print("Selected MC version:", selected_version.version_number +
(f" with {selected_version.modloader}" if selected_version.is_modified else ""))
return selected_version
if not versions:
return None
print("Installed MC versions: ")
for id, version in enumerate(versions):
print(id + 1,
version.version_number + (f" with {version.modloader}" if version.is_modified else ""),
sep=": ")
if len(versions) > 1:
id_to_use = -1
while id_to_use not in range(1, len(versions)):
id_input = input(f"Select MC version to use [1-{len(versions)}]: ")
if not id_input.isnumeric():
continue
id_to_use = int(id_input)
else:
id_to_use = 1
selected_version = versions[id_to_use - 1]
print("Selected MC version:", selected_version.version_number +
(f" with {selected_version.modloader}" if selected_version.is_modified else ""))
return selected_version
properties = db.get_properties()
if not properties:
version_to_use = __select_version(mcfs.get_installed_mc_versions())
if version_to_use.is_modified: # TODO: Добавить иерархию каталогов
db.set_properties(version_to_use.version_number, version_to_use.modloader)
if version_to_use:
if version_to_use.is_modified: # TODO: Добавить иерархию каталогов
db.set_properties(version_to_use.version_number, version_to_use.modloader)
else:
db.set_properties(version_to_use.version_number)
properties = db.get_properties()
else:
db.set_properties(version_to_use.version_number)
properties = db.get_properties()
print(properties)
_, mc_ver, loader, _ = properties
properties = None
if properties:
_, mc_ver, loader, _ = properties
else:
mc_ver, loader = None, None
desc = "Minecraft mods packet manager based on Modrinth API"
parser = argparse.ArgumentParser(description=desc,
@ -184,7 +308,7 @@ if __name__ == "__main__":
parser_install = subparsers.add_parser("install", help="Install one or more mods or resources")
parser_install.add_argument("projects", nargs="+")
parser_install.add_argument("--version", default=mc_ver)
parser_install.add_argument("--mc_ver", default=mc_ver)
parser_install.add_argument("--loader", default=loader)
parser_search = subparsers.add_parser("search", help="Find a mod or a resource")
@ -194,7 +318,12 @@ if __name__ == "__main__":
parser_clean = subparsers.add_parser("clean", help="Clean the cache of this program")
parser_remove = subparsers.add_parser("remove", help="Remove installed packages")
parser_remove.add_argument("projects", nargs="+")
kwargs = vars(parser.parse_args()) # Получаем все поля получившегося Namespace и пихаем в словарь
if not properties:
exit()
globals()[kwargs.pop("method")](**kwargs) # Из глобального контекста получаем функцию с названием как в method,
# заодно вытаскивая название метода из списка аргументов,
# затем вызываем функцию с распакованным словарём в качестве аргумента

21
mcfs.py
View file

@ -3,7 +3,7 @@ from sys import platform
import shutil
import zipfile
import json
from hashlib import sha512
class MCVersion:
def __init__(self, version_number, is_modified=False, modloader=None):
@ -118,3 +118,22 @@ def get_installed_mc_versions():
pass
versions.append(mc_ver)
return sorted(versions, reverse=True)
def check_file_hash(path, ref_hash):
buffer_size = 65536
hash = sha512()
with open(path, 'rb') as f:
data = True
while data:
data = f.read(buffer_size)
hash.update(data)
return hash.hexdigest() == ref_hash
def remove(mc_dir_rel_path):
path = os.path.join(mc_dir, mc_dir_rel_path)
if is_path_exist(path):
os.remove(path)
else:
raise FileNotFoundError

View file

@ -3,6 +3,7 @@ import os
DB_VERSION = 1
class McGetDB:
def __init_db(self):
@ -10,8 +11,34 @@ class McGetDB:
db.execute('''
CREATE TABLE IF NOT EXISTS mods (
slug TEXT PRIMARY KEY NOT NULL,
install_path TEXT NOT NULL,
version TEXT NOT NULL
proj_name TEXT NOT NULL,
filename TEXT NOT NULL,
version TEXT NOT NULL,
hash TEXT NOT NULL
);''')
db.execute('''
CREATE TABLE IF NOT EXISTS resourcepacks (
slug TEXT PRIMARY KEY NOT NULL,
proj_name TEXT NOT NULL,
filename TEXT NOT NULL,
version TEXT NOT NULL,
hash TEXT NOT NULL
);''')
db.execute('''
CREATE TABLE IF NOT EXISTS shaderpacks (
slug TEXT PRIMARY KEY NOT NULL,
proj_name TEXT NOT NULL,
filename TEXT NOT NULL,
version TEXT NOT NULL,
hash TEXT NOT NULL
);''')
db.execute('''
CREATE TABLE IF NOT EXISTS modpacks (
slug TEXT PRIMARY KEY NOT NULL,
proj_name TEXT NOT NULL,
filename TEXT NOT NULL,
version TEXT NOT NULL,
hash TEXT NOT NULL
);''')
db.execute('''
CREATE TABLE IF NOT EXISTS properties (
@ -26,6 +53,21 @@ class McGetDB:
self.db = sqlite3.connect(self.db_path)
self.__init_db()
self.add_mod = self.__db_insert("mods")
self.add_resourcepack = self.__db_insert("resourcepacks")
self.add_shader = self.__db_insert("shaderpacks")
self.add_modpack = self.__db_insert("modpacks")
self.select_mod = self.__db_select_by_col("mods", "slug")
self.select_resourcepack = self.__db_select_by_col("resourcepacks", "slug")
self.select_shader = self.__db_select_by_col("shaderpacks", "slug")
self.select_modpack = self.__db_select_by_col("modpacks", "slug")
self.remove_mod = self.__db_remove_by_col("mods", "slug")
self.remove_resourcepack = self.__db_remove_by_col("resourcepacks", "slug")
self.remove_shader = self.__db_remove_by_col("shaderpacks", "slug")
self.remove_modpack = self.__db_remove_by_col("modpacks", "slug")
def get_properties(self):
properties = None
with self.db as db:
@ -34,7 +76,7 @@ class McGetDB:
''').fetchone()
return properties
def set_properties(self, mc_ver, modloader = "NULL", dir_hierarhy = "default"):
def set_properties(self, mc_ver, modloader="NULL", dir_hierarhy="default"):
with self.db as db:
db.execute('''
DELETE FROM properties;
@ -43,11 +85,31 @@ class McGetDB:
INSERT INTO properties VALUES (?, ?, ?, ?)
''', (DB_VERSION, mc_ver, modloader, dir_hierarhy))
def add_mod(self):
pass
def __db_insert(self, table):
def insertion_func(**kargs):
keys = []
values = []
for key, value in kargs.items():
keys.append(key)
values.append(value)
with self.db as db:
db.execute(f'''
INSERT INTO {table} ({', '.join([key for key in keys])}) VALUES ({', '.join(['?' for _ in values])})
''', values)
return insertion_func
def remove_mod(self):
pass
def __db_select_by_col(self, table, col):
def selection_func(col_value):
with self.db as db:
res = db.execute(f'SELECT * FROM {table} WHERE {col} = "{col_value}"')
return res.fetchall()
return selection_func
def __db_remove_by_col(self, table, col):
def removal_func(col_value):
with self.db as db:
db.execute(f'DELETE FROM {table} WHERE {col} = "{col_value}"')
return removal_func
def update_mod(self):
pass