Skip to content
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1782d37
Fix typo in comment
Borgvall Jun 16, 2024
71ed09a
Refactor: simplify if-else
Borgvall Jun 30, 2024
89078e2
Add btrfs-progs module to flatpak build
Borgvall Nov 20, 2025
e6aa2ad
Empty BtrfsSubvolumeManager
Borgvall Jun 30, 2024
a5f8bf2
Create bottles as btrfs subvolumes
Borgvall Jun 30, 2024
07385f7
BottleSnapshotsHandle class
Borgvall Jul 4, 2024
95128af
Initialize BottleSnapshotsHandle._snapshots lazily
Borgvall Jul 2, 2024
8c11950
Free method and rename it
Borgvall Jul 2, 2024
53bbce9
First working btrfs snapshot creation
Borgvall Jul 2, 2024
0587b67
Return meaningful timestamps of subvolume snapshots
Borgvall Jul 8, 2024
44696ec
Save active btrfs snapshot ID
Borgvall Jul 8, 2024
f742b5b
Refactor perform FVS versioning work in dedicated class
Borgvall Jul 10, 2024
2add6cc
Refactor move code from manager to model module
Borgvall Jul 13, 2024
e68f63c
Delete bottles' btrfs snapshots during deletion
Borgvall Jul 13, 2024
26185cc
Add btrfsutil to the requirements
Borgvall Jul 13, 2024
98d8942
Restore previous state if the restore from snapshot fails
Borgvall Jul 13, 2024
145a5bd
Duplicate bottle as subvolumes
Borgvall Jul 13, 2024
24225aa
Fix some pylint warnings
Borgvall Jul 13, 2024
b691e20
fix _delete_subvolume()
Borgvall Nov 21, 2025
6ec85be
Fix: fallback on non btrfs filesystems
Borgvall Nov 29, 2025
16ed850
Fix: restore missed chenges of rebasing
Borgvall Nov 29, 2025
e4f90bc
Create bottles as plain subvolumes
Borgvall Dec 3, 2025
a5171c9
Fix btrfs-progs' tag-pattern
Borgvall Dec 3, 2025
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 @@ -25,6 +25,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 Task, TaskManager
Expand Down Expand Up @@ -186,26 +187,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()
21 changes: 11 additions & 10 deletions bottles/backend/managers/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,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 @@ -152,6 +153,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 @@ -1461,17 +1463,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 @@ -1685,14 +1684,15 @@ def components_check():

# 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 @@ -2089,6 +2089,7 @@ def delete_bottle(self, config: BottleConfig) -> bool:

logging.info("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