Symbolic links support for project export/import

This commit is contained in:
grossmj committed 2024-10-19 15:49:23 +10:00
1 parent cb46c0fbcc
commit 24bfc205db
6 files changed
+105 -25

No files matched your search

+10 -13
View File
@@ -70,14 +70,15 @@ async def export_project(zstream, project, temporary_dir, include_images=False,
files = [f for f in files if _is_exportable(os.path.join(root, f), include_snapshots)]
for file in files:
path = os.path.join(root, file)
# check if we can export the file
try:
open(path).close()
except OSError as e:
msg = "Could not export file {}: {}".format(path, e)
log.warning(msg)
project.emit_notification("log.warning", {"message": msg})
continue
if not os.path.islink(path):
try:
# check if we can export the file
open(path).close()
except OSError as e:
msg = "Could not export file {}: {}".format(path, e)
log.warning(msg)
project.emit_notification("log.warning", {"message": msg})
continue
# ignore the .gns3 file
if file.endswith(".gns3"):
continue
@@ -128,7 +129,7 @@ def _patch_mtime(path):
if sys.platform.startswith("win"):
# only UNIX type platforms
return
st = os.stat(path)
st = os.stat(path, follow_symlinks=False)
file_date = datetime.fromtimestamp(st.st_mtime)
if file_date.year < 1980:
new_mtime = file_date.replace(year=1980).timestamp()
@@ -144,10 +145,6 @@ def _is_exportable(path, include_snapshots=False):
if include_snapshots is False and path.endswith("snapshots"):
return False
# do not export symlinks
if os.path.islink(path):
return False
# do not export directories of snapshots
if include_snapshots is False and "{sep}snapshots{sep}".format(sep=os.path.sep) in path:
return False
+21
View File
@@ -17,6 +17,7 @@
import os
import sys
import stat
import json
import uuid
import shutil
@@ -93,6 +94,7 @@ async def import_project(controller, project_id, stream, location=None, name=Non
try:
with zipfile.ZipFile(stream) as zip_file:
await wait_run_in_executor(zip_file.extractall, path)
_create_symbolic_links(zip_file, path)
except zipfile.BadZipFile:
raise aiohttp.web.HTTPConflict(text="Cannot extract files from GNS3 project (invalid zip)")
@@ -174,6 +176,24 @@ async def import_project(controller, project_id, stream, location=None, name=Non
project = await controller.load_project(dot_gns3_path, load=False)
return project
def _create_symbolic_links(zip_file, path):
"""
Manually create symbolic links (if any) because ZipFile does not support it.
:param zip_file: ZipFile instance
:param path: project location
"""
for zip_info in zip_file.infolist():
if stat.S_ISLNK(zip_info.external_attr >> 16):
symlink_target = zip_file.read(zip_info.filename).decode()
symlink_path = os.path.join(path, zip_info.filename)
try:
# remove the regular file and replace it by a symbolic link
os.remove(symlink_path)
os.symlink(symlink_target, symlink_path)
except OSError as e:
raise aiohttp.web.HTTPConflict(text=f"Cannot create symbolic link: {e}")
def _move_node_file(path, old_id, new_id):
"""
@@ -257,6 +277,7 @@ async def _import_snapshots(snapshots_path, project_name, project_id):
with open(snapshot_path, "rb") as f:
with zipfile.ZipFile(f) as zip_file:
await wait_run_in_executor(zip_file.extractall, tmpdir)
_create_symbolic_links(zip_file, tmpdir)
except OSError as e:
raise aiohttp.web.HTTPConflict(text="Cannot open snapshot '{}': {}".format(os.path.basename(snapshot), e))
except zipfile.BadZipFile:
+1 -1
View File
@@ -1277,7 +1277,7 @@ class Project:
new_project_id = str(uuid.uuid4())
new_project_path = p_work.joinpath(new_project_id)
# copy dir
await wait_run_in_executor(shutil.copytree, self.path, new_project_path.as_posix())
await wait_run_in_executor(shutil.copytree, self.path, new_project_path.as_posix(), symlinks=True, ignore_dangling_symlinks=True)
log.info("Project content copied from '{}' to '{}' in {}s".format(self.path, new_project_path, time.time() - t0))
topology = json.loads(new_project_path.joinpath('{}.gns3'.format(self.name)).read_bytes())
project_name = name or topology["name"]