def add_mod(collection_id, mod_id): """ Adds a mod to a collection :param collection_id: int :param mod_id: int :return: None """ try: collection_id = int(collection_id) except ValueError: logger.critical("Collection id is not a valid integer") raise try: mod_id = int(mod_id) except ValueError: logger.critical("Mod id is not a valid integer") raise logger.info("Adding mod {} to collection {}".format(mod_id, collection_id)) coll = Collection.query.get(collection_id) mod = Mod.query.get(mod_id) if mod is not None and coll is not None: logger.info("Beginning addition...") # 1. Create symlink logger.info("Creating Symbolic link...") try: create_symbolic_link( join_path(config["application_root"], "mods", mod.filename), join_path(config["application_root"], "collections", coll.mc_id.decode(), "mods", get_file_name(mod.filename))) except FileExistsError: logger.warning("File already exists, continuing...") logger.info("Symlink created") # 2. Add relationship to database, for all mods in this file for m in Mod.query.filter(Mod.filename == mod.filename): logger.debug( "Adding mod: {!r} to collection: {!r} in database".format( m, coll)) coll.add_mod(m) db_session.commit() logger.info("Mod added successfully") elif mod is None: logger.error("Mod does not exist") raise exceptions.ModNotExistError(mod_id) else: logger.error("Collection does not exist") raise exceptions.CollectionNotExistError(collection_id)
def commit(self, launcher_profile_loc=join_path(config["minecraft_directory"], "launcher_profiles.json")): """ Saves profile to launcher_profiles.json, overwriting what previously had the same id as this :param launcher_profile_loc: string, absolute path to the launcher_profiles.json :return: None """ with open(launcher_profile_loc) as data_file: tmp = json.load(data_file) tmp["profiles"][self.id.decode()] = self.dictify() with open(join_path(launcher_profile_loc), 'w') as data_file: json.dump(tmp, data_file)
def __init__(self, loc): self.loc = loc self.json_loc = glob.glob(join_path(loc, "**.json"))[0] print(self.json_loc, end="\n\n") with open(self.json_loc) as x: self.info = json.load(x)
def open_file_browser(id): c = getcoll(id) util.open_file_browser( util.join_path(config["application_root"], "collections", c.mc_id.decode())) return jsonify(dict(status="success"))
def get_profiles(): """ Gets all of the profiles which are currently in launcher_profiles.json :return: list<dict> """ with open( join_path(config["minecraft_directory"], "launcher_profiles.json")) as p: profile_json = json.load(p)["profiles"] #pprint.pprint(profile_json) profiles = [] for id, data in profile_json.items(): #print("{}: {}".format(id, data)) if id in Collection.query.filter(Collection.mc_id).all( ): # if the id is the same as the id of one in our db print("{}: {}".format(id, data)) # it's one of ours: profiles.append( Profile(id, data["name"], data["lastVersionId"], data["gameDir"], data["type"], data["created"], data["lastUsed"])) return profiles
def get_collection_path(self): """ Returns an absolute path to the collection's directory :return: string """ return util.join_path(config["application_root"], "collections", self.mc_id.decode())
def rem_profile(id, launcher_profile_loc=join_path(config["minecraft_directory"], "launcher_profiles.json")): """ Removes a Profile from it's id :param id: string :return: None """ with open(launcher_profile_loc) as data_file: tmp = json.load(data_file) try: del tmp["profiles"][id.decode()] except KeyError: pass with open(join_path(launcher_profile_loc), 'w') as data_file: json.dump(tmp, data_file)
def remove(self, launcher_profile_loc=join_path(config["minecraft_directory"], "launcher_profiles.json")): """ Removes Profile from launcher_profiles.json :param launcher_profile_loc: string, absolute path to the launcher_profiles.json :return: None """ with open(launcher_profile_loc) as data_file: tmp = json.load(data_file) try: del tmp["profiles"][self.id.decode()] except KeyError: pass with open(join_path(launcher_profile_loc), 'w') as data_file: json.dump(tmp, data_file)
def _get_curse_mods(): """ Entrypoint for getting and parsing Curse mods. This should be started in a new thread. :return: None """ dl_folder = util.join_path( config["application_root"], "tmp", "cursedata", ) dl_loc = util.join_path(dl_folder, "cursedata.json.bz2") json_loc = util.join_path(dl_folder, "cursedata.json") if not os.path.exists(dl_folder): logger.warning( "Directory: {} doesn't exist yet, creating...".format(dl_folder)) os.makedirs(dl_folder) logger.info("Getting Curse mods...") try: _download_json(dl_loc) except Exception: logger.error( "Moddata download failed, maybe the computer is offline. Some Advanced features will be unavailable until reconnection occurs" ) return logger.info("Extracting archive...") _extract_json(dl_loc, json_loc) logger.info("Deleting archive...") try: os.unlink(dl_loc) except Exception as ex: logger.error("File Unlink failed with error: {}: {}".format( type(ex).__class__, ex.args)) else: logger.info("Archive deletion complete") logger.info("Parsing extracted curse mod data...") _parse_moddata(json_loc)
def __init__(self, config_loc=join_path(dirname(abspath(__file__)), 'config.json')): self.config_loc = config_loc if not os.path.exists(self.config_loc): print("Config file doesn't exist. Creating...") with open(self.config_loc, 'w') as f: json.dump(DEFAULT_CONFIG, f) with open(self.config_loc) as config_file: self.json = json.load(config_file)
def get_versions(): """ Gets all of the valid versions, which are currently installed :return: list<Version> """ versions = [] for jsonfile in glob.glob( join_path(config["minecraft_directory"], "versions", "*", "")): versions.append(Version(jsonfile)) return versions
def rem_mod(collection_id, mod_id): """ Removes a mod from the collection :param collection_id: int, the id of the collection :param mod_id: int, id of the mod :return: None """ try: collection_id = int(collection_id) except ValueError: logger.critical("Collection id is not a valid integer") raise try: mod_id = int(mod_id) except ValueError: logger.critical("Mod id is not a valid integer") raise logger.info("Removing mod with id: {} from collection with id: {}".format( mod_id, collection_id)) coll = Collection.query.get(collection_id) mod = Mod.query.get(mod_id) if mod is not None and coll is not None: logger.info("Mod and Collection exist, proceeding to removal...") if mod in coll.mods: # check if the mod is in this collection logger.info("Removing symbolic link...") try: os.unlink( join_path(config["application_root"], "collections", coll.mc_id.decode(), "mods", get_file_name(mod.filename))) except FileNotFoundError: logger.warning("Symlink already missing, continuing...") logger.info("Symlink removed") logger.info("Removing link from database...") for m in Mod.query.filter( and_(Mod.collections.any(id=collection_id), Mod.filename == mod.filename)): logger.debug("Removing Mod: {!r}".format(m)) coll.rem_mod(m) db_session.commit() logger.info("Database commit successful") else: logger.error("Mod is not in this collection, no action required") logger.info("Mod removal successful") elif mod is None: logger.error("Mod does not exist") raise exceptions.ModNotExistError(mod_id) else: logger.error("Collection doesn't exist") raise exceptions.CollectionNotExistError(collection_id)
def upload_mod(): """ Ability to upload a mod file to be added to the mod pool :return: """ if request.method == 'POST': logger.info("Got mod upload request with files: {}".format( request.files)) # check there's a file: if 'file' not in request.files: logger.error("No file part found") return jsonify( dict(status="error", title="No mod to upload", body="Please select a mod file to upload.", code="InvalidFile")) file = request.files['file'] logger.info("Got file: {} with filename: `{}`".format( file, file.filename)) if file.filename == '': logger.error("file.filename is empty") return jsonify( dict(status="error", title="No mod to upload", body="Please select a mod file to upload.", code="InvalidFile")) if file and m.allowed_modfile(file.filename): filename = secure_filename(file.filename) temp_path = join_path(UPLOADS_DIR, filename) if not os.path.exists(UPLOADS_DIR): os.makedirs(UPLOADS_DIR) file.save(temp_path) try: modid = m.add(temp_path, ignore_modexist_error=resolve_url_bool( request.args.get("overwrite"))) except exceptions.ModExistsError: return jsonify( dict(status="error", title="No mods found", body="Please select a valid mod file to upload.", code="ModExistsError" # try again with arg ignore_modexist_error=True )) else: return jsonify(dict(status="success", modid=modid))
def get_version_names(): """ Gets all of the valid versions, which are currently installed :return: list<String> """ versions = [] for jsonfile in glob.glob( join_path(config["minecraft_directory"], "versions", "*", "*.json")): with open(jsonfile) as x: j = json.load(x) versions.append(j["id"]) return versions
def curse_mod(): """ Download a mod from Curse, populating it's metadata in the database :return: """ if request.method == 'POST': if not request.args.get("curse_id"): abort(400) if not request.args.get("file_id"): abort(400) if not request.args.get("url"): abort(400) # get download url url = request.args.get("url") try: # download the mod temp_path = join_path(config["application_root"], "tmp", "uploads", urlsplit(url).path.split('/')[-1]) download_to_file(url, temp_path) try: return jsonify( dict(status='success', modid=m.add(temp_path, url, curse_id=request.args.get("curse_id"), curse_file_id=request.args.get("file_id"), ignore_modexist_error=resolve_url_bool( request.args.get("overwrite"))))) except exceptions.ModExistsError: return jsonify( dict(status='error', title="Mod already exists", body="Please select a valid mod file to download.", code="ModExistsError")) except HTTPError as e: logger.error("Failed to download Modfile: {} ({} - {})".format( e.filename, type(e), e)) abort(e.code) except URLError as e: logger.error("Failed to download ModFile: {}".format(e.filename)) abort(400) except Exception: raise
def _create_collection_filesystem(root, mc_id, folders=("mods", "config", "saves", "resourcepacks")): """ Creates the filesystem for a Collection :param root: string, the location which contains all Collections' folders :param mc_id: string, the mc_id of the collection :param folders: tuple<string>, all of the subfolders to create for the collection :return: string, absolute path to the collection's root folder """ # create the path up to <root>/<name>/ coll_root = join_path(root, mc_id.decode()) print("Creating Folder: {}".format(coll_root)) if os.path.exists(coll_root): shutil.rmtree(coll_root) os.makedirs(coll_root) for folder in folders: # create all of the sub folders for this collection os.mkdir(join_path(coll_root, folder)) return coll_root # e.g. /tfff1/SimpleLauncher/collections/<name> or C:\tfff1\SimpleLauncher\collections\<name>
def add(name, mcversion, version_id): """ Adds a new collection (if it doesn't exist already) to the DB, Minecraft, & the FileSystem :param name: string, user-friendly name of the Collection :param mcversion: string, general Minecraft Version for the collection. e.g. 1.7.10 :param version_id: string, exact name of a specific version of minecraft. e.g. 1.7.10-Forge10.13.4.1558-1.7.10 :return: int, id of the newly created Collection """ logger.info("Adding collection: {}, {}, {}".format(name, mcversion, version_id)) if not name_exists( name): # if a Collection by this name doesn't yet exist: # 1. Add it to the database logger.info("Adding to database") c = Collection(name, mcversion, version_id) db_session.add(c) db_session.commit() logger.info("Committed to database") # 2. Creates it's directory and filesystem logger.info("Creating filesystem...") coll_loc = _create_collection_filesystem( join_path(config["application_root"], "collections"), c.mc_id) logger.info("Filesystem created") # 3. Add it to Minecraft as a Profile logger.info("Adding Minecraft Profile") prof = mc_interface.Profile(c.mc_id, name, version_id, coll_loc) prof.commit() logger.info("New Profile committed") print( mc_interface.Profile(c.mc_id, name, version_id, coll_loc).dictify()) return c.id else: logger.error( "A Collection with the name: {} already exists".format(name)) raise exceptions.CollectionExistsError( str(Collection.query.filter(Collection.name == name).first().id))
def update_1_0_to_1_1(config_file_path): # type: (str) -> str print("upgrading from version 1.0 to 1.1...") logging.debug("upgrading from version 1.0 to 1.1...") with open(config_file_path, 'r') as f: config = json.load(f) # add new values (set to default) config["database_path"] = join_path(config["application_root"], "moddata.sqlite") config["webserver_port"] = 4000 # remove unneeded values # update modified values config["version"] = "1.1" # write out again with open(config_file_path, 'w') as f: json.dump(config, f) return "1.1"
def download_mod(): """ Ability to download a mod file to be added to the mod pool, from a direct download link :return: """ if request.method == 'POST': if not request.args.get("url"): abort(400) else: try: temp_path = join_path( config["application_root"], "tmp", "uploads", urlsplit(request.args.get("url")).path.split('/')[-1]) download_to_file(request.args.get("url"), temp_path) try: return redirect("/mod/{}".format( m.add(temp_path, request.args.get("url"), ignore_modexist_error=resolve_url_bool( request.args.get("overwrite"))))) except exceptions.ModExistsError: return render_template('confirm.html', title="Confirmation required", desc="") except HTTPError as e: logger.error("Failed to download Modfile: {}".format( e.filename)) abort(e.code) except URLError as e: logger.error("Failed to download ModFile: {}".format( e.filename)) abort(400) except Exception: raise
def _add_after_infoparse(file, info, dl_url, ignore_modexist_error=False, curse_id=None, curse_file_id=None): """ Does things after parsing of the file :param file: string, absolute path to the modfile (located somewhere temporarily, in APPLICATION_ROOT/tmp/uploads/<file>) :param info: MCModFile :param dl_url: string, URL the mod was downloaded from, if it was acquired via direct DL :param ignore_modexist_error: bool, whether or not to ignore the ModExistsError :return: int, id of the mod """ # 1. Move the file in to the mod folder # i. ensure that the directory exists logger.info("Creating directories as required") os.makedirs(join_path(config["application_root"], "mods"), exist_ok=True) # ii. Check if the file already exists: final_filename = get_file_name(file) final_filepath = join_path(config["application_root"], "mods", final_filename) logger.info( "Determined path for mod file to be: {}".format(final_filepath)) if os.path.exists(final_filepath): try: logger.warning("Final path exists! Figuring out what to do...") # it is already present in the directory. Compare the files logger.debug("Found filename of path: {} to be: {}".format( file, get_file_name(file))) for existing in Mod.query.filter( or_(Mod.filename == get_file_name(file), Mod.modid.in_([i.modid for i in info.mods]))).all(): # for every mod with the same filename, or with the same modid. #logger.debug("Found matching mod in database: {}".format(existing)) for mod in info.mods: logger.debug( "Checking existing mod: {} against {} found in modfile" .format(existing, mod)) if existing.modid == mod.modid and existing.version == mod.version: # they are the same mod, at the same version. Do they want a double up? logger.error( "Mod file has the same modid, at the same version") raise exceptions.ModExistsError(existing.id) else: continue else: logger.info( "No exact match for name and version exists in our database. Generating a unique file name..." ) # they're different. Fix the naming thing final_filename = find_unique_name( join_path(config["application_root"], "mods"), get_file_name(file)) final_filepath = join_path(config["application_root"], "mods", final_filename) except exceptions.ModExistsError: if not ignore_modexist_error: # clean up the mod file from temp logger.warning( "ignore_modexist_error flag set to False, so cancelling operation." ) try: logger.debug( "Attempting to clean up modfile at: {}".format(file)) os.unlink(file) except OSError: logger.warning( "Failed to clean modfile at: {}".format(file)) pass raise else: # We think that this is a duplicate, but we've been told to add it anyway so, generate new filename logger.info( "ignore_modexist_error flag set to True, so continuing operation with renamed file." ) # fix the file naming thing final_filename = find_unique_name( join_path(config["application_root"], "mods"), get_file_name(file)) final_filepath = join_path(config["application_root"], "mods", final_filename) # iii. perform the move logger.info("Moving {} to {}".format(file, final_filepath)) shutil.move( # shutil because it can handle operations across disks (e.g. C drive to F drive) unlike os.rename file, final_filepath) logger.info("Move successful. Adding mods to database...") # 2. Add to db if info.mods: logger.debug("Mods present, beginning add...") info.add_to_database(final_filename, dl_url, curse_id=None, curse_file_id=None) else: logger.error("No Mods are found in the mod file") raise exceptions.ModNotExistError() logger.info("Finished adding mod file.") return Mod.query.order_by(Mod.id.desc()).first().id
from os.path import dirname, abspath import logging logger = logging.getLogger(__name__) VERSIONS = ["1.0", "1.1"] """ These functions migrate from the key version up to the config version specified by the function's return value. """ CONFIG_UPDATERS = { "1.0": lambda: None, } DEFAULT_CONFIG = { "version": "1.0", "application_root": join_path(get_default_tfff1_loc(), 'SimpleLauncher'), "minecraft_directory": get_default_mc_loc(), "fetch_curse_moddata": True, "logging": { "version": 1, "formatters": { "f": { "format": "(%(name)s) [%(levelname)s]: %(message)s" } }, "handlers": { 'fh': { 'class': 'logging.handlers.RotatingFileHandler', 'formatter': 'f', 'level': logging.INFO, 'maxBytes': 1000000,
def remove(id): """ Removes a mod from the filesystem and database, including deletion of all links to collections :param id: int :return: None """ logger.info("Beginning removal of mod with id: {}".format(id)) # 0. Get file name: logger.debug("Getting Mod from database...") m = Mod.query.get(id) if m is None: logger.error("Mod doesn't exist in database") raise exceptions.ModNotExistError(id) filename = m.filename if filename is None: logger.critical( "Database entry is missing mission-critical filename data. Throwing error..." ) raise exceptions.ModInfoNotExistError(id, 'filename') # 1. Remove all symlinks in Collections logger.info("Removing from collections: {}".format(m.collections)) for coll in m.collections: logger.debug("Working with collection {}...".format(coll.id)) logger.debug("Removing Symbolic link...") try: os.unlink( join_path(config["application_root"], "collections", coll.mc_id.decode(), "mods", filename)) except FileNotFoundError as ex: logger.error("Symlink doesn't exist") except OSError as ex: logger.critical( "Insufficient Permissions for Symbolic link manipulation: {}". format(ex.args)) logger.debug("Symlink removed successfully") logger.debug("Removing relationship...") coll.rem_mod(m) logger.debug("Relationship removed") logger.info("Finished removing from collections") # 2. Delete mod file: logger.info("Deleting mod file...") os.remove(join_path(config["application_root"], "mods", filename)) logger.info("Deletion successful") # 3. Delete Mod from database logger.info("Removing from database...") for mod in Mod.query.filter(Mod.filename == m.filename): logger.debug("Removing mod: {!r} from database".format(mod)) db_session.delete(mod) logger.info("Removed") logger.debug("Committing Database changes...") db_session.commit() logger.debug("Commit successful") logger.info("Finished Removing mod with id {}".format(id))
import os from os.path import dirname, abspath, isfile import logging from .migrator import CONFIG_UPDATERS class ConfigUpdateError(Exception): pass logger = logging.getLogger(__name__) VERSIONS = ["1.0", "1.1"] HISTORIC_CONFIG_LOCATIONS = [ # the zero'th item should be the current location # from latest to oldest join_path(get_default_tfff1_loc(), "SimpleLauncher", "config.json"), join_path(dirname(dirname(abspath(__file__))), "config.json") ] DEFAULT_CONFIG = { "version": "1.1", "application_root": join_path(get_default_tfff1_loc(), 'SimpleLauncher'), "minecraft_directory": get_default_mc_loc(), "fetch_curse_moddata": True, "database_path": join_path(get_default_tfff1_loc(), 'SimpleLauncher', 'moddata.sqlite'), "webserver_port":
import logging import os from flask import Blueprint, request, flash, render_template, redirect, abort, jsonify from werkzeug.utils import secure_filename from simple_mod_installer import mod as m, config from simple_mod_installer.util import join_path, resolve_url_bool from simple_mod_installer.util.http import download_to_file from simple_mod_installer import exceptions, searchmods from urllib.error import URLError, HTTPError from urllib.parse import urlsplit mod = Blueprint('mod', __name__, template_folder='../../templates') logger = logging.getLogger(__name__) UPLOADS_DIR = join_path(config["application_root"], "tmp", "uploads") ''' @mod.route('/') def index(): return render_template('mod_index.html', mods=m.all()) @mod.route('/<id>') def view_mod(id): """ Displays a Mod :param id: int, is of the mod """ try: return render_template('mod_view.html', mod=m.get_from_id(id)) except exceptions.ModNotExistError: # a collection with this id doesn't exist abort(404)