Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Versioning based on btrfs snapshots #3420

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 22 additions & 19 deletions bottles/backend/managers/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from bottles.backend.globals import Paths
from bottles.backend.logger import Logger
from bottles.backend.managers.manager import Manager
from bottles.backend.models.btrfssubvolume import duplicate_bottle_as_subvolume, DuplicateResult
from bottles.backend.models.config import BottleConfig
from bottles.backend.models.result import Result
from bottles.backend.state import TaskManager, Task
Expand Down Expand Up @@ -185,26 +186,28 @@ def _duplicate_bottle_directory(
config: BottleConfig, source_path: str, destination_path: str, new_name: str
) -> Result:
try:
if not os.path.exists(destination_path):
duplicate_result = duplicate_bottle_as_subvolume(source_path, destination_path)
if not duplicate_result.destination_directories_created():
os.makedirs(destination_path)
for item in [
"drive_c",
"system.reg",
"user.reg",
"userdef.reg",
"bottle.yml",
]:
source_item = os.path.join(source_path, item)
destination_item = os.path.join(destination_path, item)
if os.path.isdir(source_item):
shutil.copytree(
source_item,
destination_item,
ignore=shutil.ignore_patterns(".*"),
symlinks=True,
)
elif os.path.isfile(source_item):
shutil.copy(source_item, destination_item)
if not duplicate_result.bottle_contents_is_duplicated():
for item in [
"drive_c",
"system.reg",
"user.reg",
"userdef.reg",
"bottle.yml",
]:
source_item = os.path.join(source_path, item)
destination_item = os.path.join(destination_path, item)
if os.path.isdir(source_item):
shutil.copytree(
source_item,
destination_item,
ignore=shutil.ignore_patterns(".*"),
symlinks=True,
)
elif os.path.isfile(source_item):
shutil.copy(source_item, destination_item)

# Update the bottle configuration
config_path = os.path.join(destination_path, "bottle.yml")
Expand Down
22 changes: 22 additions & 0 deletions bottles/backend/managers/btrfssubvolume.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import bottles.backend.models.btrfssubvolume as btrfssubvolume

class BtrfsSubvolumeManager:
"""
Manager to handle bottles created as btrfs subvolume.
"""

def __init__(
self,
manager,
):
self._manager = manager

@staticmethod
def create_bottle_as_subvolume(bottle_path) -> bool:
return btrfssubvolume.create_bottle_as_subvolume(bottle_path)

@staticmethod
def delete_all_snapshots(bottle_path):
snapshots_handle = btrfssubvolume.try_create_bottle_snapshots_handle(bottle_path)
if snapshots_handle:
snapshots_handle.delete_all_snapshots()
33 changes: 17 additions & 16 deletions bottles/backend/managers/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from bottles.backend.dlls.vkd3d import VKD3DComponent
from bottles.backend.globals import Paths
from bottles.backend.logger import Logger
from bottles.backend.managers.btrfssubvolume import BtrfsSubvolumeManager
from bottles.backend.managers.component import ComponentManager
from bottles.backend.managers.data import DataManager, UserDataKeys
from bottles.backend.managers.dependency import DependencyManager
Expand Down Expand Up @@ -145,6 +146,7 @@ def __init__(
times["RepositoryManager"] = time.time()
self.versioning_manager = VersioningManager(self)
times["VersioningManager"] = time.time()
self.btrfs_subvolume_manager = BtrfsSubvolumeManager(self)
self.component_manager = ComponentManager(self, _offline)
self.installer_manager = InstallerManager(self, _offline)
self.dependency_manager = DependencyManager(self, _offline)
Expand Down Expand Up @@ -1060,17 +1062,14 @@ def create_bottle_from_config(self, config: BottleConfig) -> bool:
# create the bottle path
bottle_path = os.path.join(Paths.bottles, config.Name)

if not os.path.exists(bottle_path):
"""
If the bottle does not exist, create it, else
append a random number to the name.
"""
os.makedirs(bottle_path)
else:
# If the bottle exists append a random number to the name.
if os.path.exists(bottle_path):
rnd = random.randint(100, 200)
bottle_path = f"{bottle_path}__{rnd}"
config.Name = f"{config.Name}__{rnd}"
config.Path = f"{config.Path}__{rnd}"

if not self.btrfs_subvolume_manager.create_bottle_as_subvolume(bottle_path):
os.makedirs(bottle_path)

# Pre-create drive_c directory and set the case-fold flag
Expand Down Expand Up @@ -1232,22 +1231,17 @@ def components_check():
bottle_name_path = f"{bottle_name_path}__{rnd}"
bottle_complete_path = f"{bottle_complete_path}__{rnd}"

# define registers that should be awaited
reg_files = [
os.path.join(bottle_complete_path, "system.reg"),
os.path.join(bottle_complete_path, "user.reg"),
]

# create the bottle directory
try:
os.makedirs(bottle_complete_path)
if not self.btrfs_subvolume_manager.create_bottle_as_subvolume(bottle_complete_path):
os.makedirs(bottle_complete_path)
# Pre-create drive_c directory and set the case-fold flag
bottle_drive_c = os.path.join(bottle_complete_path, "drive_c")
os.makedirs(bottle_drive_c)
FileUtils.chattr_f(bottle_drive_c)
except:
except RuntimeError as e:
logging.error(
f"Failed to create bottle directory: {bottle_complete_path}", jn=True
f"Failed to create bottle directory '{bottle_complete_path}' {e}", jn=True
)
log_update(_("Failed to create bottle directory."))
return Result(False)
Expand Down Expand Up @@ -1353,6 +1347,12 @@ def components_check():
os.unlink(link)
os.makedirs(link)

# define registers that should be awaited
reg_files = [
os.path.join(bottle_complete_path, "system.reg"),
os.path.join(bottle_complete_path, "user.reg"),
]

# wait for registry files to be created
FileUtils.wait_for_files(reg_files)

Expand Down Expand Up @@ -1557,6 +1557,7 @@ def delete_bottle(self, config: BottleConfig) -> bool:

logging.info(f"Removing the bottle…")
path = ManagerUtils.get_bottle_path(config)
self.btrfs_subvolume_manager.delete_all_snapshots(path)
subprocess.run(["rm", "-rf", path], stdout=subprocess.DEVNULL)

self.update_bottles(silent=True)
Expand Down
1 change: 1 addition & 0 deletions bottles/backend/managers/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ managersdir = join_paths(pkgdatadir, 'bottles/backend/managers')
bottles_sources = [
'__init__.py',
'backup.py',
'btrfssubvolume.py',
'component.py',
'dependency.py',
'installer.py',
Expand Down
Loading