Merge branch '2.2' into 3.0

# Conflicts:
#	gns3server/controller/__init__.py
#	gns3server/controller/export_project.py
#	gns3server/controller/project.py
#	gns3server/controller/snapshot.py
#	gns3server/crash_report.py
#	gns3server/schemas/snapshot.py
#	gns3server/static/web-ui/26.34a83d9cee7db39cd0de.js
#	gns3server/static/web-ui/index.html
#	gns3server/static/web-ui/main.d74d38241f0602d1b3e0.js
#	gns3server/version.py
#	gns3server/web/route.py
#	tests/handlers/api/controller/test_project.py
#	tests/handlers/api/controller/test_snapshot.py
This commit is contained in:
grossmj committed 2026-04-11 19:22:05 +08:00
commit 0d3fad8c8f
23 files changed
+432 -204

No files matched your search

+47 -18
View File
@@ -36,32 +36,47 @@ import logging
log = logging.getLogger(__name__)
# Used to extract the date and time from the filename
FILENAME_DATETIME_FORMAT = "%d%m%y_%H%M%S"
# Used to create a description of the snapshot with a human-readable date and time
DESCRIPTION_DATETIME_FORMAT = "%Y-%m-%d at %H:%M:%S"
class Snapshot:
"""
A snapshot object
"""
def __init__(self, project, name=None, filename=None):
def __init__(self, project, snapshot_id=None, name=None, filename=None, created_at=None, description=None):
assert filename or name, "You need to pass a name or a filename"
self._id = str(
uuid.uuid4()
) # We don't need to keep id between project loading because they are use only as key for operation like delete, update.. but have no impact on disk
if snapshot_id:
self._id = snapshot_id
else:
self._id = str(uuid.uuid4())
self._project = project
if name:
self._name = name
self._created_at = datetime.now(timezone.utc).timestamp()
filename = (
self._name
+ "_"
+ datetime.fromtimestamp(self._created_at, tz=timezone.utc).replace(tzinfo=None).strftime("%d%m%y_%H%M%S")
+ ".gns3project"
)
if created_at:
self._created_at = created_at
else:
self._created_at = int(datetime.now(timezone.utc).timestamp())
if not filename:
filename = self._name + ".gns3snapshot"
else:
self._name = filename.rsplit("_", 2)[0]
datestring = filename.replace(self._name + "_", "").split(".")[0]
self._created_at = (datetime.strptime(datestring, "%d%m%y_%H%M%S").replace(tzinfo=timezone.utc).timestamp())
self._created_at = int(datetime.strptime(datestring, FILENAME_DATETIME_FORMAT).replace(tzinfo=timezone.utc).timestamp())
if not description:
date = datetime.fromtimestamp(self._created_at, tz=timezone.utc).replace(tzinfo=None).strftime(DESCRIPTION_DATETIME_FORMAT)
description = "Snapshot '{}' taken on {}".format(self._name, date)
self._description = description
self._filename = filename
self._path = os.path.join(project.path, "snapshots", filename)
@property
@@ -72,13 +87,17 @@ class Snapshot:
def name(self):
return self._name
@property
def description(self):
return self._description
@property
def path(self):
return self._path
@property
def created_at(self):
return int(self._created_at)
return self._created_at
async def create(self):
"""
@@ -117,14 +136,22 @@ class Snapshot:
await self._project.close(ignore_notification=True)
try:
begin = time.time()
# delete the current project files
project_files_path = os.path.join(self._project.path, "project-files")
if os.path.exists(project_files_path):
await wait_run_in_executor(shutil.rmtree, project_files_path)
with open(self._path, "rb") as f:
project = await import_project(self._project.controller, self._project.id, f, location=self._project.path,
auto_start=self._project.auto_start, auto_open=self._project.auto_open,
auto_close=self._project.auto_close)
project = await import_project(
self._project.controller,
self._project.id,
f,
location=self._project.path,
auto_start=self._project.auto_start,
auto_open=self._project.auto_open,
auto_close=self._project.auto_close
)
log.info("Snapshot '{}' restored in {:.4f} seconds".format(self.name, time.time() - begin))
except (OSError, PermissionError) as e:
raise ControllerError(str(e))
await project.open()
@@ -135,6 +162,8 @@ class Snapshot:
return {
"snapshot_id": self._id,
"name": self._name,
"created_at": int(self._created_at),
"project_id": self._project.id,
"created_at": self._created_at,
"description": self._description,
"filename": self._filename,
"project_id": self._project.id
}