9.0 KiB
📁 Organisation des fichiers par hostname
Vue d'ensemble
Le système d'upload a été amélioré pour organiser automatiquement les fichiers et images par hostname de device dans des sous-dossiers structurés.
Structure précédente
uploads/
├── 3562b30f85326e79_3.jpg
├── 7660e368d0cb566e_4.png
├── 8b5371f003d8616f_3.png
├── ec199bc98be16a37_3.pdf
└── peripherals/
Nouvelle structure
uploads/
├── srv-proxmox/
│ ├── images/
│ │ ├── 3562b30f85326e79_3.jpg
│ │ └── 7660e368d0cb566e_4.png
│ └── files/
│ └── ec199bc98be16a37_3.pdf
├── rpi4-cluster-01/
│ ├── images/
│ │ └── a1b2c3d4e5f67890_1.jpg
│ └── files/
│ └── datasheet_5.pdf
└── peripherals/
└── (unchanged)
Avantages
- Organisation claire : Les fichiers sont regroupés par device
- Séparation images/fichiers : Facilite la gestion et les sauvegardes
- Scalabilité : Fonctionne avec des milliers de devices
- Navigation facile : Accès direct aux fichiers d'un device
- Nettoyage simplifié : Suppression d'un device = suppression d'un dossier
Fonctionnement
Détection automatique
Le système détecte automatiquement si un fichier est une image :
Extensions d'images :
.jpg,.jpeg.png.gif.webp.bmp.svg
Type MIME :
- Tout MIME type commençant par
image/
Sanitisation des noms
Les hostnames sont nettoyés pour être utilisables comme noms de dossiers :
# Exemples de sanitisation
"srv-proxmox.local" → "srv-proxmox.local"
"my server (old)" → "my_server_old"
"test@2024" → "test_2024"
"___test___" → "test"
Règles :
- Caractères interdits remplacés par
_ - Points et tirets conservés
- Underscores multiples condensés
- Longueur limitée à 100 caractères
- Fallback sur "unknown" si vide
Migration des fichiers existants
Un script de migration est fourni pour réorganiser les fichiers existants.
Dry-run (simulation)
Pour voir ce qui serait fait sans modifier les fichiers :
python backend/migrate_file_organization.py
Sortie exemple :
================================================================================
File Organization Migration
================================================================================
Found 15 documents to migrate
Mode: DRY RUN
--------------------------------------------------------------------------------
📄 Document 1 (image):
Device: srv-proxmox (ID: 3)
From: ./uploads/3562b30f85326e79_3.jpg
To: ./uploads/srv-proxmox/images/3562b30f85326e79_3.jpg
[DRY RUN - would migrate]
📄 Document 5 (file):
Device: srv-proxmox (ID: 3)
From: ./uploads/ec199bc98be16a37_3.pdf
To: ./uploads/srv-proxmox/files/ec199bc98be16a37_3.pdf
[DRY RUN - would migrate]
...
Summary:
Migrated: 12
Skipped: 2
Errors: 1
Total: 15
This was a DRY RUN. To actually migrate files, run:
python backend/migrate_file_organization.py --execute
Migration réelle
Pour effectuer réellement la migration :
python backend/migrate_file_organization.py --execute
Avec nettoyage
Pour migrer ET supprimer les dossiers vides :
python backend/migrate_file_organization.py --execute --cleanup
Utilisation de l'API
Upload d'un document
L'API détecte automatiquement le type et place le fichier au bon endroit :
# Upload d'une image
curl -X POST "http://localhost:8007/api/devices/3/docs" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@photo.jpg" \
-F "doc_type=photo"
# Sera stocké dans: uploads/srv-proxmox/images/hash_3.jpg
# Upload d'un PDF
curl -X POST "http://localhost:8007/api/devices/3/docs" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@manual.pdf" \
-F "doc_type=manual"
# Sera stocké dans: uploads/srv-proxmox/files/hash_3.pdf
Téléchargement
Le téléchargement utilise toujours le même endpoint :
curl "http://localhost:8007/api/docs/123/download" \
-H "Authorization: Bearer YOUR_TOKEN" \
-o document.pdf
Le système lit le stored_path en base de données qui contient le chemin complet.
Architecture technique
Module file_organizer.py
from app.utils.file_organizer import (
sanitize_hostname,
get_device_upload_paths,
ensure_device_directories,
get_upload_path,
is_image_file
)
Fonctions principales :
sanitize_hostname(hostname: str) -> str
Nettoie un hostname pour utilisation comme nom de dossier.
get_device_upload_paths(base_dir: str, hostname: str) -> Tuple[str, str]
Retourne les chemins (images, files) pour un device.
ensure_device_directories(base_dir: str, hostname: str) -> Tuple[str, str]
Crée les dossiers s'ils n'existent pas et retourne les chemins.
get_upload_path(base_dir: str, hostname: str, is_image: bool, filename: str) -> str
Retourne le chemin complet où stocker un fichier.
is_image_file(filename: str, mime_type: str = None) -> bool
Détermine si un fichier est une image.
Modification de docs.py
Avant :
stored_path = os.path.join(settings.UPLOAD_DIR, stored_filename)
os.makedirs(settings.UPLOAD_DIR, exist_ok=True)
Après :
is_image = is_image_file(file.filename, file.content_type)
stored_path = get_upload_path(
settings.UPLOAD_DIR,
device.hostname,
is_image,
stored_filename
)
Compatibilité
Anciens fichiers
Les fichiers existants continuent de fonctionner grâce au stored_path en base de données :
- Les anciens chemins (
uploads/hash_id.ext) restent valides - Les nouveaux uploads utilisent la nouvelle structure
- La migration est optionnelle mais recommandée
Téléchargement
L'API de téléchargement utilise le stored_path de la base de données, donc :
- ✅ Anciens fichiers : fonctionnent
- ✅ Nouveaux fichiers : fonctionnent
- ✅ Fichiers migrés : fonctionnent
Cas d'usage
Sauvegarde sélective
# Sauvegarder seulement les images d'un device
rsync -av uploads/srv-proxmox/images/ backup/srv-proxmox-images/
# Sauvegarder tous les PDF
find uploads/*/files -name "*.pdf" -exec cp {} backup/pdfs/ \;
Nettoyage par device
# Supprimer tous les fichiers d'un device désinstallé
rm -rf uploads/old-server/
Audit de l'espace
# Voir l'espace utilisé par device
du -sh uploads/*/
# Sortie :
# 45M uploads/srv-proxmox/
# 120M uploads/rpi4-cluster-01/
# 2.3M uploads/laptop-dev/
Migration progressive
Vous pouvez migrer progressivement :
-
Phase 1 : Déployer le nouveau code
- Nouveaux uploads utilisent la nouvelle structure
- Anciens fichiers restent en place
-
Phase 2 : Tester la migration
- Faire un dry-run
- Vérifier les chemins générés
-
Phase 3 : Migrer en production
- Exécuter la migration réelle
- Vérifier que les téléchargements fonctionnent
-
Phase 4 : Nettoyage
- Nettoyer les dossiers vides
- Archiver les anciens fichiers si nécessaire
Sécurité
Validation
- Les noms de fichiers sont hashés (pas de conflit de noms)
- Les hostnames sont sanitisés (pas d'injection de chemin)
- Les tailles de fichiers sont vérifiées
- Les extensions sont validées
Isolation
- Chaque device a son propre dossier
- Pas de risque de collision entre devices
- Permissions préservées
Performance
Impact
- ✅ Création de dossiers : négligeable (mkdir -p)
- ✅ Upload : identique à avant
- ✅ Download : identique à avant
- ✅ Migration : proportionnel au nombre de fichiers
Optimisations
- Les dossiers sont créés une seule fois
- Pas de scans récursifs
- Utilise les fonctions OS natives
Limitations
-
Hostname changeant : Si un hostname change, les fichiers restent dans l'ancien dossier
- Solution : Script de remapping si nécessaire
-
Caractères spéciaux : Certains caractères sont remplacés par
_- C'est intentionnel pour la compatibilité filesystem
-
Périphériques : Le dossier
peripherals/garde sa propre structure- Pour éviter de casser le code existant
FAQ
Q: Que se passe-t-il si je ne migre pas les anciens fichiers ? R: Ils continuent de fonctionner normalement. Seuls les nouveaux uploads utilisent la nouvelle structure.
Q: Puis-je revenir en arrière ?
R: Oui, en modifiant les stored_path en base de données et en déplaçant les fichiers.
Q: La migration supprime-t-elle les fichiers originaux ? R: Non, elle les déplace (move, pas copy). Les fichiers ne sont pas dupliqués.
Q: Que faire si un device a le même hostname qu'un autre ? R: Les fichiers iront dans le même dossier, mais les noms de fichiers incluent le device_id donc pas de collision.
Fichiers créés :
backend/app/utils/file_organizer.py- Module utilitairebackend/migrate_file_organization.py- Script de migration
Fichiers modifiés :
backend/app/api/docs.py- Utilise la nouvelle organisation
Créé le : 2026-01-11