-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
427 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
], | ||
) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.