Skip to content

Commit

Permalink
Working app
Browse files Browse the repository at this point in the history
  • Loading branch information
Vredeza committed Dec 19, 2023
1 parent 0d874b8 commit 680f417
Show file tree
Hide file tree
Showing 15 changed files with 427 additions and 1 deletion.
3 changes: 2 additions & 1 deletion client/build.sh
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
python3 setup.py sdist bdist_wheel
python3 setup.py sdist bdist_wheel
pip install dist/Sauvegardeur-1.0.0.tar.gz
Empty file.
227 changes: 227 additions & 0 deletions client/client_package/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import requests
import argparse
import os
import zipfile


def get_file_extensions(file_path):
"""
:param file_path: Le chemin vers le fichier contenant les extensions de fichier à envoyer au serveur.
:return: Une liste contenant les extensions de fichier à envoyer au serveur.
"""
extensions = []

with open(file_path, 'r') as file:
for line in file:
extension = line.strip() # J'enlève les espaces au cas où
if extension and not extension.startswith('#'):
extensions.append(extension)

return extensions


def zip_folder_with_extensions(folder_path, extensions=None):
"""
Créer un fichier zip contenant les fichiers à envoyer au serveur. Selon les extensions spécifiées.
:param folder_path: Chemin vers le dossier à compresser.
:param extensions: Liste d'extensions de fichiers. Si vide, envoie tous les fichiers contenu dans le dossier.
:return: Le chemin vers le fichier zip créé.
"""
# Création d'un fichier .zip temporaire
if extensions is None:
extensions = []
temp_zip_path = 'temp.zip'
with zipfile.ZipFile(temp_zip_path, 'w') as zipf:
for folder_name, subfolders, filenames in os.walk(folder_path):
for filename in filenames:
file_path = os.path.join(folder_name, filename)
# Vérification de l'extension du fichier
if not extensions or any(file_path.endswith(ext) for ext in extensions):
# Ajout des fichiers avec les extensions spécifiées au fichier .zip
zipf.write(file_path, arcname=os.path.relpath(file_path, folder_path))

return temp_zip_path


def send_zip_to_server(zip_file_path, server_name, server_port):
"""
Envoie un fichier au serveur.
:param zip_file_path: Le chemin vers le fichier zip à envoyer.
:param server_name: L'addresse du serveur.
:param server_port: Le port sur lequel envoyer fichier.
"""
server_url = f'http://{server_name}:{server_port}/upload'

with open(zip_file_path, 'rb') as file:
files = {'file': file}
response = requests.post(server_url, files=files)

if response.status_code == 200:
print("Fichier envoyé avec succès au serveur !")
else:
print("Échec de l'envoi du fichier au serveur.")


def save_action(args):
# Vérification du dossier racine
if not os.path.isdir(os.path.abspath(args.dossier)):
print(f"Erreur : Le chemin spécifié pour le dossier '{args.dossier}' n'existe pas ou n'est pas un dossier "
f"valide.")
exit(1)

# Vérification du fichier extensions
if args.fichier and not os.path.isfile(os.path.abspath(args.fichier)):
print(
f"Erreur : Le chemin spécifié pour le fichier '{args.fichier}' n'existe pas ou n'est pas un fichier valide.")
exit(1)

if args.fichier:
zip_path = zip_folder_with_extensions(args.dossier, get_file_extensions(args.fichier))
else:
zip_path = zip_folder_with_extensions(args.dossier)

send_zip_to_server(zip_path, args.adresse, args.port)
os.remove(zip_path)


def ls_action(args):
url = f'http://{args.adresse}:{args.port}/saves'
response = requests.get(url)

if response.status_code == 200:
data = response.json()
directories = data.get('directories', [])

# Affichage sous forme de tableau
print("ID\t\t\t\t\tDate\t\t\tSize")
print("----------------------------------------------------------------------")
for directory in directories:
name = directory.get('name', '')
size = directory.get('size', 0)
date = directory.get('creation_time', '')
print(f"{name}\t{date}\t{size}")
else:
print("Erreur lors de la récupération des informations de sauvegarde.")


def restore_action(args):
full_id = get_full_id(args.sauvegarde, args.adresse, args.port)
if full_id == '':
print("Impossible de faire correspondre l'identifiant de la sauvegarde.")
exit(1)

url = f'http://{args.adresse}:{args.port}/save/{full_id}'

# Effectuer la requête GET pour obtenir le fichier ZIP
response = requests.get(url)

if response.status_code == 200:
# Chemin où sauvegarder le fichier ZIP téléchargé
zip_file_path = 'temp_restore.zip'

# Écriture du contenu du fichier ZIP
with open(zip_file_path, 'wb') as file:
file.write(response.content)

# Décompresser le fichier ZIP dans le dossier spécifié
with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
zip_ref.extractall(args.destination)

# Supprimer le fichier ZIP temporaire après extraction
os.remove(zip_file_path)

print("Restauration terminée.")
else:
print("Erreur lors de la restauration.")


def get_full_id(incomplete_id, server, port):
url = f'http://{server}:{port}/saves'

response = requests.get(url)

if response.status_code == 200:
data = response.json()
directories = data.get('directories', [])

# Stocker les correspondances potentielles trouvées
matches = [directory['name'] for directory in directories if incomplete_id in directory['name']]

# Filtrer les correspondances pour obtenir l'ID complet
full_ids = [match for match in matches if match.startswith(incomplete_id)]

if len(full_ids) == 1:
return full_ids[0] # Retourne l'ID complet unique correspondant à l'ID partiel donné
else:
return '' # Plusieurs correspondances ou aucune
else:
print("Erreur lors de la récupération des informations de sauvegarde.")
return ''


def tree_action(args):
url = f'http://{args.adresse}:{args.port}/save/{get_full_id(args.id, args.adresse, args.port)}/tree'
response = requests.get(url)

if response.status_code == 200:
print(response.content.decode('utf-8'))
elif response.status_code == 404:
print("Erreur : sauvegarde n'existe pas.")


def parse_arguments():
parser = argparse.ArgumentParser(
description="Client pour sauvegarder une arborescence. Réalisé dans le cadre du projet de fin de ressource "
"'Continuité de services'.")

subparsers = parser.add_subparsers(title='commands', dest='command')

# Commande 'save'
save_parser = subparsers.add_parser('save', help='Sauvegarde une arborescence')
save_parser.add_argument('dossier', help="Chemin du dossier à sauvegarder")
save_parser.add_argument('-e', '--fichier',
help="Chemin du fichier contenant, si nécessaire, les extensions à envoyer.")
save_parser.add_argument('-s', '--adresse', default='localhost', help="Adresse IP du serveur (défaut : localhost)")
save_parser.add_argument('-p', '--port', type=int, default=80, help="Numéro de port du serveur (défaut : 80)")

# Commande 'ls'
ls_parser = subparsers.add_parser('ls', help='Liste les sauvegardes disponibles')
ls_parser.add_argument('-s', '--adresse', default='localhost', help="Adresse IP du serveur (défaut : localhost)")
ls_parser.add_argument('-p', '--port', type=int, default=80, help="Numéro de port du serveur (défaut : 80)")

# Commande 'restore'
restore_parser = subparsers.add_parser('restore', help='Restaure une sauvegarde')
restore_parser.add_argument('sauvegarde', help="L'id de la sauvegarde à restaurer.")
restore_parser.add_argument('-d', '--destination', help="Chemin de destination pour la restauration", required=True)
restore_parser.add_argument('-s', '--adresse', default='localhost',
help="Adresse IP du serveur (défaut : localhost)")
restore_parser.add_argument('-p', '--port', type=int, default=80, help="Numéro de port du serveur (défaut : 80)")

tree_parser = subparsers.add_parser('tree', help="Affiche l'arborescence d'une sauvegarde.")
tree_parser.add_argument('id', help="L'id de la sauvegarde à visualiser.")
tree_parser.add_argument('-s', '--adresse', default='localhost', help="Adresse IP du serveur (défaut : localhost)")
tree_parser.add_argument('-p', '--port', type=int, default=80, help="Numéro de port du serveur (défaut : 80)")

return parser.parse_args()


def main():
args = parse_arguments()

if args.command == 'save':
save_action(args)
elif args.command == 'ls':
ls_action(args)
elif args.command == 'restore':
restore_action(args)
elif args.command == 'tree':
tree_action(args)
else:
print("Commande non reconnue")


if __name__ == "__main__":
main()


15 changes: 15 additions & 0 deletions client/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from setuptools import setup, find_packages

setup(
name='Sauvegardeur',
version='1.0.0',
packages=find_packages(),
entry_points={
'console_scripts': [
'sauvegardeur = client_package.client:main'
]
},
install_requires=[
'requests'
],
)
Binary file added images/ls.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/tree.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions serveur/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.10.12

WORKDIR /app

COPY . /app/

RUN pip install --no-cache-dir -r requirements.txt

CMD ["python3", "-u", "serveur.py"]
16 changes: 16 additions & 0 deletions serveur/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from flask import Flask
from .saves.routes import saves_bp
from .upload.routes import upload_bp


def create_app():
app = Flask(__name__)

app.register_blueprint(saves_bp)
app.register_blueprint(upload_bp)

@app.route("/", methods=["GET"])
def get_root():
return "Racine du serveur."

return app
Empty file added serveur/app/saves/__init__.py
Empty file.
63 changes: 63 additions & 0 deletions serveur/app/saves/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import os
import zipfile

from flask import Blueprint, jsonify, send_file

from ..utils import get_directory_size, get_creation_time, obtenir_arborescence

saves_bp = Blueprint('saves', __name__)


@saves_bp.route('/saves', methods=['GET'])
def get_saves():
"""
Renvoie la liste des différentes sauvegardes contenues dans le serveur (leurs dates).
:return: Un objet contenant la date et le poids de chaque sauvegarde.
"""
stored_files_path = 'stored_files'
directories = []

if os.path.exists(stored_files_path) and os.path.isdir(stored_files_path):
for entry in os.scandir(stored_files_path):
if entry.is_dir():
dir_info = {
'id': entry.name,
'size': get_directory_size(entry.path),
'creation_time': get_creation_time(entry.path)
}
directories.append(dir_info)

return jsonify({'directories': directories})


@saves_bp.route('/save/<id>', methods=['GET'])
def get_save_by_id(id):
save_path = 'stored_files/'
save_folder_path = os.path.join(save_path, id)

# Vérifier si le dossier spécifié par l'ID existe
if os.path.exists(save_folder_path) and os.path.isdir(save_folder_path):
# Créer un fichier ZIP temporaire pour le dossier spécifié
temp_zip_path = f"{id}.zip"
with zipfile.ZipFile(temp_zip_path, 'w') as zipf:
for folder_name, _, filenames in os.walk(save_folder_path):
for filename in filenames:
file_path = os.path.join(folder_name, filename)
zipf.write(file_path, arcname=os.path.relpath(file_path, save_folder_path))

# Envoyer le fichier ZIP en réponse à la requête GET
return send_file(temp_zip_path, as_attachment=True)
else:
return 'Not Found', 404 # Le dossier n'existe pas, renvoie un code 404


@saves_bp.route('/save/<id>/tree', methods=['GET'])
def get_save_tree_by_id(id):
save_path = 'stored_files/'
save_folder_path = os.path.join(save_path, id)

# Vérifier si le dossier spécifié par l'ID existe
if os.path.exists(save_folder_path) and os.path.isdir(save_folder_path):
return obtenir_arborescence(os.path.join(save_path, id)), 200 # Le dossier existe, renvoie un code 200
else:
return 'Not Found', 404 # Le dossier n'existe pas, renvoie un code 404
Empty file added serveur/app/upload/__init__.py
Empty file.
53 changes: 53 additions & 0 deletions serveur/app/upload/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import os
import uuid
import zipfile
from datetime import datetime

from flask import Blueprint, request

upload_bp = Blueprint('upload', __name__)


@upload_bp.route('/upload', methods=['POST'])
def upload_file():
"""
Sauvegarde le contenu du fichier zip passé dans le corps de la requête dans un dossier nommé par la date.
:return: Un code HTTP, 200 ou 400.
"""
if 'file' not in request.files:
return 'Aucun fichier envoyé.', 400

uploaded_file = request.files['file']

if uploaded_file.filename == '':
return 'Nom de fichier vide.', 400

save_path = 'stored_files/'
if not os.path.exists(save_path):
os.makedirs(save_path)

now = datetime.now()
folder_name = str(uuid.uuid4()) # Format de nom de dossier basé sur la date et l'heure actuelles
folder_path = os.path.join(save_path, folder_name)

# Création du dossier pour extraire le contenu du fichier ZIP
os.makedirs(folder_path)

# Sauvegarde du fichier ZIP dans le dossier avec le nom original
zip_path = os.path.join(folder_path, uploaded_file.filename)
uploaded_file.save(zip_path)

# Vérifier si le fichier est un fichier .zip
if uploaded_file.filename.endswith('.zip'):
# Dézipper le fichier dans le dossier créé
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(folder_path)

# Supprimer le fichier ZIP après extraction
os.remove(zip_path)

return 'Fichier .zip reçu et décompressé avec succès !', 200
else:
# Supprimer le fichier ZIP s'il n'est pas au format .zip
os.remove(zip_path)
return 'Le fichier n\'est pas un fichier .zip.', 400
Loading

0 comments on commit 680f417

Please sign in to comment.