diff --git a/default_config.py b/default_config.py index 881039f..5d4d125 100644 --- a/default_config.py +++ b/default_config.py @@ -19,3 +19,8 @@ BASE_URL="http://localhost:5000" # Session expiration, in days MAX_AGE=31 + +# Instance path ie. where are all the files + the database stored +# Keep in mind that this config option can only be loaded from default_config.py, +# as the custom config is stored in $INSTANCE_PATH/ +INSTANCE_PATH="instance" diff --git a/make.sh b/make.sh index 967c26f..1da7302 100755 --- a/make.sh +++ b/make.sh @@ -1,25 +1,27 @@ #!/bin/bash +INSTANCE_PATH="instance" + init () { - mkdir -p "instance" - mkdir -p "partitioncloud/partitions" - mkdir -p "partitioncloud/attachments" - mkdir -p "partitioncloud/search-partitions" - mkdir -p "partitioncloud/static/thumbnails" - mkdir -p "partitioncloud/static/search-thumbnails" + mkdir -p "$INSTANCE_PATH" + mkdir -p "$INSTANCE_PATH/partitions" + mkdir -p "$INSTANCE_PATH/attachments" + mkdir -p "$INSTANCE_PATH/search-partitions" + mkdir -p "$INSTANCE_PATH/static/thumbnails" + mkdir -p "$INSTANCE_PATH/static/search-thumbnails" - if ! test -f "instance/config.py"; then - echo "SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex())')" > instance/config.py + if ! test -f "$INSTANCE_PATH/config.py"; then + echo "SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex())')" > "$INSTANCE_PATH/config.py" fi - if test -f "instance/partitioncloud.sqlite"; then + if test -f "$INSTANCE_PATH/partitioncloud.sqlite"; then printf "Souhaitez vous supprimer la base de données existante ? [y/n] " read -r CONFIRMATION [[ $CONFIRMATION == y ]] || exit 1 fi - sqlite3 "instance/partitioncloud.sqlite" '.read partitioncloud/schema.sql' + sqlite3 "$INSTANCE_PATH/partitioncloud.sqlite" '.read partitioncloud/schema.sql' echo "Base de données créé" - sqlite3 "instance/partitioncloud.sqlite" '.read partitioncloud/init.sql' + sqlite3 "$INSTANCE_PATH/partitioncloud.sqlite" '.read partitioncloud/init.sql' echo "Utilisateur root:root ajouté" } @@ -43,7 +45,7 @@ usage () { if [[ $1 && $(type "$1") = *"is a"*"function"* || $(type "$1") == *"est une fonction"* ]]; then # Import config source "default_config.py" - [[ ! -x instance/config.py ]] && source "instance/config.py" + [[ ! -x" $INSTANCE_PATH/config.py" ]] && source "$INSTANCE_PATH/config.py" $1 ${*:2} # Call the function else usage diff --git a/partitioncloud/__init__.py b/partitioncloud/__init__.py index b7283c2..f61915d 100644 --- a/partitioncloud/__init__.py +++ b/partitioncloud/__init__.py @@ -3,8 +3,10 @@ Main file """ import os +import sys import datetime import subprocess +import importlib.util from flask import Flask, g, redirect, render_template, request, send_file, flash, session, abort from werkzeug.security import generate_password_hash @@ -16,14 +18,48 @@ from .modules.db import get_db app = Flask(__name__) -app.config.from_mapping( - DATABASE=os.path.join(app.instance_path, f"{__name__}.sqlite"), -) -app.config.from_object('default_config') -if os.path.exists("instance/config.py"): - app.config.from_object('instance.config') -else: - print("[WARNING] Using default config") + +def load_config(): + app.config.from_object('default_config') + app.instance_path = os.path.abspath(app.config["INSTANCE_PATH"]) + + if not os.path.exists(app.instance_path): + print("[ERROR] Instance path does not exist. Make sure to use an existing directory.") + sys.exit(1) + + if os.path.exists(f"{app.instance_path}/config.py"): + # Load module from instance_path/config.py in user_config object + spec = importlib.util.spec_from_file_location( + ".", + os.path.join(app.instance_path, "config.py") + ) + user_config = importlib.util.module_from_spec(spec) + spec.loader.exec_module(user_config) + + app.config.from_object(user_config) + + if os.path.abspath(app.config["INSTANCE_PATH"]) != app.instance_path: + print("[ERROR] Using two different instance path. \ + \nPlease modify INSTANCE_PATH only in default_config.py and remove it from $INSTANCE_PATH/config.py") + sys.exit(1) + else: + print("[WARNING] Using default config") + + app.config.from_mapping( + DATABASE=os.path.join(app.instance_path, f"{__name__}.sqlite"), + ) + + +def get_version(): + try: + result = subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE, check=True) + return result.stdout.decode('utf8') + except (FileNotFoundError, subprocess.CalledProcessError): + # In case git not found or any platform specific weird error + return "unknown" + + +load_config() app.register_blueprint(auth.bp) app.register_blueprint(admin.bp) @@ -31,13 +67,7 @@ app.register_blueprint(groupe.bp) app.register_blueprint(albums.bp) app.register_blueprint(partition.bp) - -try: - result = subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE, check=True) - __version__ = result.stdout.decode('utf8') -except (FileNotFoundError, subprocess.CalledProcessError): - # In case git not found or any platform specific weird error - __version__ = "unknown" +__version__ = get_version() @app.route("/") @@ -99,7 +129,7 @@ def search_thumbnail(uuid): f'/usr/bin/convert -thumbnail\ "178^>" -background white -alpha \ remove -crop 178x178+0+0 \ - partitioncloud/search-partitions/{uuid}.pdf[0] \ + {app.instance_path}/search-partitions/{uuid}.pdf[0] \ partitioncloud/static/search-thumbnails/{uuid}.jpg' ) diff --git a/partitioncloud/modules/albums.py b/partitioncloud/modules/albums.py index 7561ec7..defa440 100644 --- a/partitioncloud/modules/albums.py +++ b/partitioncloud/modules/albums.py @@ -42,7 +42,7 @@ def search_page(): query = request.form["query"] nb_queries = abs(int(request.form["nb-queries"])) - search.flush_cache() + search.flush_cache(current_app.instance_path) partitions_local = search.local_search(query, utils.get_all_partitions()) user = User(user_id=session.get("user_id")) @@ -52,7 +52,7 @@ def search_page(): nb_queries = min(current_app.config["MAX_ONLINE_QUERIES"], nb_queries) else: nb_queries = min(10, nb_queries) # Query limit is 10 for an admin - google_results = search.online_search(query, nb_queries) + google_results = search.online_search(query, nb_queries, current_app.instance_path) else: google_results = [] @@ -207,7 +207,7 @@ def delete_album(uuid): flash(error) return redirect(request.referrer) - album.delete() + album.delete(current_app.instance_path) flash("Album supprimé.") return redirect("/albums") @@ -278,20 +278,32 @@ def add_partition(album_uuid): ) db.commit() + partition_path = os.path.join( + current_app.instance_path, + "partitions", + f"{partition_uuid}.pdf" + ) + if partition_type == "file": file = request.files["file"] - file.save(f"partitioncloud/partitions/{partition_uuid}.pdf") + file.save(partition_path) else: + search_partition_path = os.path.join( + current_app.instance_path, + "search-partitions", + f"{search_uuid}.pdf" + ) + shutil.copyfile( - f"partitioncloud/search-partitions/{search_uuid}.pdf", - f"partitioncloud/partitions/{partition_uuid}.pdf" + search_partition_path, + partition_path ) os.system( f'/usr/bin/convert -thumbnail\ "178^>" -background white -alpha \ remove -crop 178x178+0+0 \ - partitioncloud/partitions/{partition_uuid}.pdf[0] \ + {partition_path}[0] \ partitioncloud/static/thumbnails/{partition_uuid}.jpg' ) db.commit() diff --git a/partitioncloud/modules/classes/album.py b/partitioncloud/modules/classes/album.py index cdb03f8..7e048fd 100644 --- a/partitioncloud/modules/classes/album.py +++ b/partitioncloud/modules/classes/album.py @@ -82,7 +82,7 @@ class Album(): ).fetchall() - def delete(self): + def delete(self, instance_path): """ Supprimer l'album """ @@ -130,9 +130,9 @@ class Album(): attachments = [Attachment(data=i) for i in data] for attachment in attachments: - attachment.delete() + attachment.delete(instance_path) - os.remove(f"partitioncloud/partitions/{partition['uuid']}.pdf") + os.remove(f"{instance_path}/partitions/{partition['uuid']}.pdf") if os.path.exists(f"partitioncloud/static/thumbnails/{partition['uuid']}.jpg"): os.remove(f"partitioncloud/static/thumbnails/{partition['uuid']}.jpg") diff --git a/partitioncloud/modules/classes/attachment.py b/partitioncloud/modules/classes/attachment.py index 902b264..44adf79 100644 --- a/partitioncloud/modules/classes/attachment.py +++ b/partitioncloud/modules/classes/attachment.py @@ -29,7 +29,7 @@ class Attachment(): self.filetype = data["filetype"] self.partition_uuid = data["partition_uuid"] - def delete(self): + def delete(self, instance_path): db = get_db() db.execute( """ @@ -40,7 +40,7 @@ class Attachment(): ) db.commit() - os.remove(f"partitioncloud/attachments/{self.uuid}.{self.filetype}") + os.remove(f"{instance_path}/attachments/{self.uuid}.{self.filetype}") def __repr__(self): return f"{self.name}.{self.filetype}" diff --git a/partitioncloud/modules/classes/groupe.py b/partitioncloud/modules/classes/groupe.py index 19398a8..1ba9301 100644 --- a/partitioncloud/modules/classes/groupe.py +++ b/partitioncloud/modules/classes/groupe.py @@ -21,7 +21,7 @@ class Groupe(): self.albums = None self.admins = None - def delete(self): + def delete(self, instance_path): """ Supprime le groupe, et les albums laissés orphelins (sans utilisateur) """ @@ -63,7 +63,7 @@ class Groupe(): for i in data: album = Album(id=i["id"]) - album.delete() + album.delete(instance_path) def get_users(self): diff --git a/partitioncloud/modules/classes/partition.py b/partitioncloud/modules/classes/partition.py index 9815f91..b8465b2 100644 --- a/partitioncloud/modules/classes/partition.py +++ b/partitioncloud/modules/classes/partition.py @@ -29,7 +29,7 @@ class Partition(): else: raise LookupError - def delete(self): + def delete(self, instance_path): self.load_attachments() db = get_db() @@ -42,7 +42,7 @@ class Partition(): ) db.commit() - os.remove(f"partitioncloud/partitions/{self.uuid}.pdf") + os.remove(f"{instance_path}/partitions/{self.uuid}.pdf") if os.path.exists(f"partitioncloud/static/thumbnails/{self.uuid}.jpg"): os.remove(f"partitioncloud/static/thumbnails/{self.uuid}.jpg") @@ -56,7 +56,7 @@ class Partition(): db.commit() for attachment in self.attachments: - attachment.delete() + attachment.delete(instance_path) def update(self, name=None, author="", body=""): if name is None: diff --git a/partitioncloud/modules/groupe.py b/partitioncloud/modules/groupe.py index f30c19f..fe03429 100644 --- a/partitioncloud/modules/groupe.py +++ b/partitioncloud/modules/groupe.py @@ -3,7 +3,7 @@ Groupe module """ from flask import (Blueprint, abort, flash, redirect, render_template, - request, session) + request, session, current_app) from .auth import login_required from .db import get_db @@ -155,7 +155,7 @@ def delete_groupe(uuid): flash(error) return redirect(request.referrer) - groupe.delete() + groupe.delete(current_app.instance_path) flash("Groupe supprimé.") return redirect("/albums") diff --git a/partitioncloud/modules/partition.py b/partitioncloud/modules/partition.py index f6076d9..700b221 100644 --- a/partitioncloud/modules/partition.py +++ b/partitioncloud/modules/partition.py @@ -4,7 +4,8 @@ Partition module """ import os from uuid import uuid4 -from flask import Blueprint, abort, send_file, render_template, request, redirect, flash, session +from flask import (Blueprint, abort, send_file, render_template, + request, redirect, flash, session, current_app) from .db import get_db from .auth import login_required, admin_required @@ -21,9 +22,11 @@ def get_partition(uuid): abort(404) - return send_file( - os.path.join("partitions", f"{uuid}.pdf"), - download_name = f"{partition.name}.pdf" + return send_file(os.path.join( + current_app.instance_path, + "partitions", + f"{uuid}.pdf" + ), download_name = f"{partition.name}.pdf" ) @bp.route("//attachments") @@ -51,7 +54,7 @@ def add_attachment(uuid): user = User(user_id=session.get("user_id")) if user.id != partition.user_id and user.access_level != 1: - flash("Cette partition ne vous appartient pas") + flash("Cette partition ne vous current_appartient pas") return redirect(request.referrer) error = None # À mettre au propre @@ -90,7 +93,11 @@ def add_attachment(uuid): db.commit() file = request.files["file"] - file.save(f"partitioncloud/attachments/{attachment_uuid}.{ext}") + file.save(os.path.join( + current_app.instance_path, + "attachments", + f"{attachment_uuid}.{ext}" + )) break except db.IntegrityError: @@ -114,9 +121,11 @@ def get_attachment(uuid, filetype): assert filetype == attachment.filetype - return send_file( - os.path.join("attachments", f"{uuid}.{attachment.filetype}"), - download_name = f"{attachment.name}.{attachment.filetype}" + return send_file(os.path.join( + current_app.instance_path, + "attachments", + f"{uuid}.{attachment.filetype}" + ), download_name = f"{attachment.name}.{attachment.filetype}" ) @@ -223,7 +232,7 @@ def delete(uuid): if request.method == "GET": return render_template("partition/delete.html", partition=partition, user=user) - partition.delete() + partition.delete(current_app.instance_path) flash("Partition supprimée.") return redirect("/albums") @@ -245,7 +254,13 @@ def partition_search(uuid): abort(404) if request.args.get("redirect") == "true" and partition["url"] is not None: return redirect(partition["url"]) - return send_file(os.path.join("search-partitions", f"{uuid}.pdf")) + + return send_file(os.path.join( + current_app.instance_path, + "search-partitions", + f"{uuid}.pdf" + ) + ) @bp.route("/") diff --git a/partitioncloud/modules/search.py b/partitioncloud/modules/search.py index 2b24b90..27c8b22 100644 --- a/partitioncloud/modules/search.py +++ b/partitioncloud/modules/search.py @@ -49,19 +49,19 @@ def local_search(query, partitions): return selection -def download_search_result(element): +def download_search_result(element, instance_path): uuid = element["uuid"] url = element["url"] try: - urllib.request.urlretrieve(url, f"partitioncloud/search-partitions/{uuid}.pdf") + urllib.request.urlretrieve(url, f"{instance_path}/search-partitions/{uuid}.pdf") except (urllib.error.HTTPError, urllib.error.URLError): - with open(f"partitioncloud/search-partitions/{uuid}.pdf", 'a', encoding="utf8") as _: + with open(f"{instance_path}/search-partitions/{uuid}.pdf", 'a', encoding="utf8") as _: pass # Create empty file -def online_search(query, num_queries): +def online_search(query, num_queries, instance_path): """ Renvoie les 3 résultats les plus pertinents depuis google """ @@ -103,7 +103,12 @@ def online_search(query, num_queries): except urllib.error.URLError: # Unable to access network return [] - threads = [threading.Thread(target=download_search_result, args=(elem,)) for elem in partitions] + threads = [ + threading.Thread( + target=download_search_result, + args=(elem, instance_path) + ) for elem in partitions + ] for thread in threads: thread.start() @@ -114,7 +119,7 @@ def online_search(query, num_queries): for element in partitions.copy(): uuid = element["uuid"] url = element["url"] - if os.stat(f"partitioncloud/search-partitions/{uuid}.pdf").st_size == 0: + if os.stat(f"{instance_path}/search-partitions/{uuid}.pdf").st_size == 0: print("An error occured", url) db.execute( """ @@ -125,14 +130,14 @@ def online_search(query, num_queries): ) db.commit() - os.remove(f"partitioncloud/search-partitions/{uuid}.pdf") + os.remove(f"{instance_path}/search-partitions/{uuid}.pdf") partitions.remove(element) return partitions -def flush_cache(): +def flush_cache(instance_path): """ Supprimer les résultats de recherche datant de plus de 15 minutes """ @@ -146,7 +151,7 @@ def flush_cache(): for element in expired_cache: uuid = element["uuid"] try: - os.remove(f"partitioncloud/search-partitions/{uuid}.pdf") + os.remove(f"{instance_path}/search-partitions/{uuid}.pdf") except FileNotFoundError: pass try: diff --git a/scripts/hooks/v1.py b/scripts/hooks/v1.py index 1a5613b..b9050f7 100644 --- a/scripts/hooks/v1.py +++ b/scripts/hooks/v1.py @@ -1,4 +1,5 @@ import os +import shutil import sqlite3 from hooks import utils from colorama import Fore, Style @@ -142,3 +143,21 @@ def base_url_parameter_added(): def install_qrcode(): os.system("pip install qrcode -qq") + + +""" + v1.5.* +""" + + +def move_instance(): + paths = [ + "attachments", + "partitions", + "search-partitions" + ] + for path in paths: + shutil.move( + os.path.join("partitioncloud", path), + os.path.join("instance", path) + ) \ No newline at end of file diff --git a/scripts/migration.py b/scripts/migration.py index 096aa73..0ac6a94 100644 --- a/scripts/migration.py +++ b/scripts/migration.py @@ -33,6 +33,7 @@ hooks = [ ], ), ("v1.4.1", [("Install qrcode", v1_hooks.install_qrcode)]), + ("v1.5.0", [("Move to instance directory", v1_hooks.move_instance)]) ] @@ -86,10 +87,10 @@ def backup_instance(version, verbose=True): os.path.join(dest, "search-partitions"), ), ] - for src, dst in paths: + for src, dst in paths: # Only the first one exists after v1.5.0 if os.path.exists(src): print_verbose(f"\tBacking up {src}") - shutil.copy_tree(src, dst) + shutil.copytree(src, dst) def print_hooks(hooks_list): @@ -108,7 +109,7 @@ def apply_hooks(hooks_list): def migrate(current, target, skip_backup=False, prog_name="scripts/migration.py"): - """""" + """Migrate from one version to another""" print(f"Trying to migrate from {current} to {target}") assert is_newer(target, current) @@ -132,7 +133,7 @@ def migrate(current, target, skip_backup=False, prog_name="scripts/migration.py" ) print( f"If something goes wrong, recover with {Style.BRIGHT}{Fore.BLUE}{prog_name}\ - --restore {current}{Style.RESET_ALL}" + --restore {current}{Style.RESET_ALL}" ) else: print("Skipping automatic backup") @@ -168,13 +169,13 @@ def restore(version): os.path.join(dest, "search-partitions"), ), ] - for src, dst in paths: + for src, dst in paths: # Only the first one exists after v1.5.0 if os.path.exists(src): shutil.rmtree(src) if os.path.exists(dst): print(f"\tRestoring {src}") - shutil.copy_tree(dst, src) + shutil.copytree(dst, src) else: print( f"\t{Fore.RED}No available backup for {src}, \