|
23 | 23 |
|
24 | 24 | import json |
25 | 25 | import os |
| 26 | +import shutil |
| 27 | +import tempfile |
| 28 | +import zipfile |
26 | 29 | from hashlib import sha256 |
27 | 30 | from http import HTTPStatus |
28 | 31 | from typing import Any, Dict, List, Optional, Tuple, Union |
|
42 | 45 | Citation, |
43 | 46 | Event, |
44 | 47 | Family, |
45 | | - GrampsType, |
46 | 48 | Media, |
47 | | - Note, |
48 | 49 | Person, |
49 | 50 | Place, |
50 | 51 | PlaceType, |
51 | | - Repository, |
52 | 52 | Source, |
53 | 53 | Span, |
54 | | - Tag, |
55 | 54 | ) |
56 | 55 | from gramps.gen.lib.primaryobj import BasicPrimaryObject as GrampsObject |
57 | | -from gramps.gen.lib.serialize import from_json, to_json |
| 56 | +from gramps.gen.lib.serialize import to_json |
58 | 57 | from gramps.gen.plug import BasePluginManager |
59 | 58 | from gramps.gen.relationship import get_relationship_calculator |
60 | 59 | from gramps.gen.soundex import soundex |
|
70 | 69 | from gramps.gen.utils.id import create_id |
71 | 70 | from gramps.gen.utils.place import conv_lat_lon |
72 | 71 |
|
| 72 | +from ...auth import set_tree_usage |
73 | 73 | from ...const import DISABLED_IMPORTERS, SEX_FEMALE, SEX_MALE, SEX_UNKNOWN |
74 | 74 | from ...types import FilenameOrPath, Handle, TransactionJson |
75 | | -from ..media import get_media_handler |
| 75 | +from ..file import get_checksum |
| 76 | +from ..media import check_quota_media, get_media_handler |
76 | 77 | from ..util import abort_with_message, get_db_handle, get_tree_from_jwt |
77 | 78 |
|
78 | 79 | pd = PlaceDisplay() |
@@ -1209,3 +1210,96 @@ def dry_run_import( |
1209 | 1210 | "notes": db_handle.get_number_of_notes(), |
1210 | 1211 | "tags": db_handle.get_number_of_tags(), |
1211 | 1212 | } |
| 1213 | + |
| 1214 | + |
| 1215 | +def run_import_media_archive( |
| 1216 | + tree: str, |
| 1217 | + db_handle: DbReadBase, |
| 1218 | + file_name: FilenameOrPath, |
| 1219 | + delete: bool = True, |
| 1220 | +) -> Dict[str, int]: |
| 1221 | + """Import a media archive file.""" |
| 1222 | + media_handler = get_media_handler(db_handle, tree=tree) |
| 1223 | + |
| 1224 | + # create a dict {checksum: [(handle1, path), (handle2, path2), ...], ...} |
| 1225 | + # of missing files |
| 1226 | + handles = db_handle.get_media_handles() |
| 1227 | + objects = [db_handle.get_media_from_handle(handle) for handle in handles] |
| 1228 | + objects_existing = media_handler.filter_existing_files(objects, db_handle=db_handle) |
| 1229 | + handles_existing = set(obj.handle for obj in objects_existing) |
| 1230 | + objects_missing = [obj for obj in objects if obj.handle not in handles_existing] |
| 1231 | + |
| 1232 | + checksums_handles: Dict[str, List[Tuple[str, str, str]]] = {} |
| 1233 | + for obj in objects_missing: |
| 1234 | + if obj.checksum not in checksums_handles: |
| 1235 | + checksums_handles[obj.checksum] = [] |
| 1236 | + obj_details = (obj.handle, obj.get_path(), obj.get_mime_type()) |
| 1237 | + checksums_handles[obj.checksum].append(obj_details) |
| 1238 | + if len(checksums_handles) == 0: |
| 1239 | + # no missing files |
| 1240 | + # delete ZIP file |
| 1241 | + if delete: |
| 1242 | + os.remove(file_name) |
| 1243 | + return {"missing": 0, "uploaded": 0, "failures": 0} |
| 1244 | + |
| 1245 | + total_size = 0 |
| 1246 | + with zipfile.ZipFile(file_name, "r") as zip_file: |
| 1247 | + # compute file size |
| 1248 | + for file_info in zip_file.infolist(): |
| 1249 | + total_size += file_info.file_size |
| 1250 | + |
| 1251 | + # check disk usage |
| 1252 | + disk_usage = shutil.disk_usage(file_name) |
| 1253 | + if total_size > disk_usage.free: |
| 1254 | + raise ValueError("Not enough free space on disk") |
| 1255 | + |
| 1256 | + # extract |
| 1257 | + temp_dir = tempfile.mkdtemp() |
| 1258 | + zip_file.extractall(temp_dir) |
| 1259 | + |
| 1260 | + # delete ZIP file |
| 1261 | + if delete: |
| 1262 | + os.remove(file_name) |
| 1263 | + |
| 1264 | + to_upload = {} |
| 1265 | + # walk extracted files |
| 1266 | + for root, _, files in os.walk(temp_dir): |
| 1267 | + for name in files: |
| 1268 | + file_path = os.path.join(root, name) |
| 1269 | + with open(file_path, "rb") as f: |
| 1270 | + checksum = get_checksum(f) |
| 1271 | + if checksum in checksums_handles and checksum not in to_upload: |
| 1272 | + to_upload[checksum] = (file_path, os.path.getsize(file_path)) |
| 1273 | + |
| 1274 | + if len(to_upload) == 0: |
| 1275 | + # no files to upload |
| 1276 | + |
| 1277 | + # delete extracted temp files |
| 1278 | + shutil.rmtree(temp_dir) |
| 1279 | + |
| 1280 | + return {"missing": len(checksums_handles), "uploaded": 0, "failures": 0} |
| 1281 | + |
| 1282 | + upload_size = sum([file_size for (file_path, file_size) in to_upload.values()]) |
| 1283 | + check_quota_media(to_add=upload_size, tree=tree) |
| 1284 | + |
| 1285 | + num_failures = 0 |
| 1286 | + for checksum, (file_path, file_size) in to_upload.items(): |
| 1287 | + for handle, media_path, mime in checksums_handles[checksum]: |
| 1288 | + with open(file_path, "rb") as f: |
| 1289 | + try: |
| 1290 | + media_handler.upload_file(f, checksum, mime, path=media_path) |
| 1291 | + except Exception: |
| 1292 | + num_failures += 1 |
| 1293 | + |
| 1294 | + # delete extracted temp files |
| 1295 | + shutil.rmtree(temp_dir) |
| 1296 | + |
| 1297 | + # update media usage |
| 1298 | + usage_media = media_handler.get_media_size(db_handle=db_handle) |
| 1299 | + set_tree_usage(tree, usage_media=usage_media) |
| 1300 | + |
| 1301 | + return { |
| 1302 | + "missing": len(checksums_handles), |
| 1303 | + "uploaded": len(to_upload) - num_failures, |
| 1304 | + "failures": num_failures, |
| 1305 | + } |
0 commit comments