python3-cyberfusion-sync-support 1.2.7.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python3-cyberfusion-sync-support-1.2.7.3.1/PKG-INFO +42 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/README.md +32 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/pyproject.toml +21 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/setup.cfg +12 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/setup.py +23 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/src/cyberfusion/SyncSupport/__init__.py +5 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/src/cyberfusion/SyncSupport/exceptions.py +28 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/src/cyberfusion/SyncSupport/unix_users.py +367 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/src/python3_cyberfusion_sync_support.egg-info/PKG-INFO +42 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/src/python3_cyberfusion_sync_support.egg-info/SOURCES.txt +11 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/src/python3_cyberfusion_sync_support.egg-info/dependency_links.txt +1 -0
- python3-cyberfusion-sync-support-1.2.7.3.1/src/python3_cyberfusion_sync_support.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python3-cyberfusion-sync-support
|
|
3
|
+
Version: 1.2.7.3.1
|
|
4
|
+
Summary: Library for syncing objects (e.g. directories).
|
|
5
|
+
Home-page: https://github.com/CyberfusionIO/python3-cyberfusion-sync-support
|
|
6
|
+
Author: Cyberfusion
|
|
7
|
+
Author-email: support@cyberfusion.io
|
|
8
|
+
Platform: linux
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# python3-cyberfusion-sync-support
|
|
12
|
+
|
|
13
|
+
Library for syncing objects (e.g. directories).
|
|
14
|
+
|
|
15
|
+
# Install
|
|
16
|
+
|
|
17
|
+
## PyPI
|
|
18
|
+
|
|
19
|
+
Run the following command to install the package from PyPI:
|
|
20
|
+
|
|
21
|
+
pip3 install python3-cyberfusion-sync-support
|
|
22
|
+
|
|
23
|
+
## Generic
|
|
24
|
+
|
|
25
|
+
Run the following command to create a source distribution:
|
|
26
|
+
|
|
27
|
+
python3 setup.py sdist
|
|
28
|
+
|
|
29
|
+
## Debian
|
|
30
|
+
|
|
31
|
+
Run the following commands to build a Debian package:
|
|
32
|
+
|
|
33
|
+
mk-build-deps -i -t 'apt -o Debug::pkgProblemResolver=yes --no-install-recommends -y'
|
|
34
|
+
dpkg-buildpackage -us -uc
|
|
35
|
+
|
|
36
|
+
# Configure
|
|
37
|
+
|
|
38
|
+
No configuration is supported.
|
|
39
|
+
|
|
40
|
+
# Usage
|
|
41
|
+
|
|
42
|
+
See code.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# python3-cyberfusion-sync-support
|
|
2
|
+
|
|
3
|
+
Library for syncing objects (e.g. directories).
|
|
4
|
+
|
|
5
|
+
# Install
|
|
6
|
+
|
|
7
|
+
## PyPI
|
|
8
|
+
|
|
9
|
+
Run the following command to install the package from PyPI:
|
|
10
|
+
|
|
11
|
+
pip3 install python3-cyberfusion-sync-support
|
|
12
|
+
|
|
13
|
+
## Generic
|
|
14
|
+
|
|
15
|
+
Run the following command to create a source distribution:
|
|
16
|
+
|
|
17
|
+
python3 setup.py sdist
|
|
18
|
+
|
|
19
|
+
## Debian
|
|
20
|
+
|
|
21
|
+
Run the following commands to build a Debian package:
|
|
22
|
+
|
|
23
|
+
mk-build-deps -i -t 'apt -o Debug::pkgProblemResolver=yes --no-install-recommends -y'
|
|
24
|
+
dpkg-buildpackage -us -uc
|
|
25
|
+
|
|
26
|
+
# Configure
|
|
27
|
+
|
|
28
|
+
No configuration is supported.
|
|
29
|
+
|
|
30
|
+
# Usage
|
|
31
|
+
|
|
32
|
+
See code.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[tool.isort]
|
|
2
|
+
profile = "black"
|
|
3
|
+
line_length = 79
|
|
4
|
+
known_first_party = ["cyberfusion"]
|
|
5
|
+
default_section = "THIRDPARTY"
|
|
6
|
+
|
|
7
|
+
[tool.black]
|
|
8
|
+
line-length = 79
|
|
9
|
+
exclude = '''
|
|
10
|
+
(
|
|
11
|
+
/(
|
|
12
|
+
\.eggs # exclude a few common directories in the
|
|
13
|
+
| \.git # root of the project
|
|
14
|
+
| \.hg
|
|
15
|
+
| \.mypy_cache
|
|
16
|
+
| \.tox
|
|
17
|
+
| \.venv
|
|
18
|
+
| venv
|
|
19
|
+
)/
|
|
20
|
+
)
|
|
21
|
+
'''
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""A setuptools based setup module."""
|
|
2
|
+
|
|
3
|
+
from setuptools import setup
|
|
4
|
+
|
|
5
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
6
|
+
long_description = fh.read()
|
|
7
|
+
|
|
8
|
+
setup(
|
|
9
|
+
name="python3-cyberfusion-sync-support",
|
|
10
|
+
version="1.2.7.3.1",
|
|
11
|
+
description="Library for syncing objects (e.g. directories).",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
author="Cyberfusion",
|
|
15
|
+
author_email="support@cyberfusion.io",
|
|
16
|
+
url="https://github.com/CyberfusionIO/python3-cyberfusion-sync-support",
|
|
17
|
+
platforms=["linux"],
|
|
18
|
+
packages=[
|
|
19
|
+
"cyberfusion.SyncSupport",
|
|
20
|
+
],
|
|
21
|
+
package_dir={"": "src"},
|
|
22
|
+
data_files=[],
|
|
23
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Exceptions."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SyncSupportError(Exception):
|
|
7
|
+
"""Error occurred using this library."""
|
|
8
|
+
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class IllegalMemberError(SyncSupportError):
|
|
14
|
+
"""Archive contains member that is not in store path."""
|
|
15
|
+
|
|
16
|
+
member_name: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StorePathNotRelativeError(SyncSupportError):
|
|
20
|
+
"""Store path is not relative (to UNIX user home directory)."""
|
|
21
|
+
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FilesystemPathNotRelativeError(SyncSupportError):
|
|
26
|
+
"""Filesystem path is not relative (to UNIX user home directory)."""
|
|
27
|
+
|
|
28
|
+
pass
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""Classes for interaction with UNIX users."""
|
|
2
|
+
|
|
3
|
+
import getpass
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import tarfile
|
|
7
|
+
from pathlib import Path, PosixPath
|
|
8
|
+
from typing import List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
from cached_property import cached_property
|
|
11
|
+
|
|
12
|
+
from cyberfusion.Common import generate_random_string, get_md5_hash
|
|
13
|
+
from cyberfusion.Common.FilesystemComparison import (
|
|
14
|
+
get_directories_only_in_right_directory,
|
|
15
|
+
get_files_only_in_right_directory,
|
|
16
|
+
)
|
|
17
|
+
from cyberfusion.SyncSupport import PATH_ARCHIVE
|
|
18
|
+
from cyberfusion.SyncSupport.exceptions import (
|
|
19
|
+
FilesystemPathNotRelativeError,
|
|
20
|
+
IllegalMemberError,
|
|
21
|
+
StorePathNotRelativeError,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class UNIXUserHomeDirectoryArchive:
|
|
26
|
+
"""Abstraction of archive of UNIX user home directory."""
|
|
27
|
+
|
|
28
|
+
EXTENSION_FILE_TAR_GZ = "tar.gz"
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
store_path: str,
|
|
34
|
+
exclude_paths: Optional[List[str]] = None,
|
|
35
|
+
archive_path_root_directory: str = PATH_ARCHIVE,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Set attributes.
|
|
38
|
+
|
|
39
|
+
The archive is written to a file inside root_directory. The default path
|
|
40
|
+
is are automatically cleaned up using systemd-tmpfiles, if this library
|
|
41
|
+
is installed as a Debian package.
|
|
42
|
+
"""
|
|
43
|
+
self.store_path = store_path
|
|
44
|
+
self.exclude_paths = exclude_paths if exclude_paths is not None else []
|
|
45
|
+
self.archive_path_root_directory = archive_path_root_directory
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def store_path(self) -> str:
|
|
49
|
+
"""Set store path.
|
|
50
|
+
|
|
51
|
+
Path in archive is relative, so is path without leading slash.
|
|
52
|
+
"""
|
|
53
|
+
return self._store_path
|
|
54
|
+
|
|
55
|
+
@store_path.setter
|
|
56
|
+
def store_path(self, value: str) -> None:
|
|
57
|
+
"""Set store path.
|
|
58
|
+
|
|
59
|
+
Checks if store path is relative.
|
|
60
|
+
"""
|
|
61
|
+
if os.path.isabs(value):
|
|
62
|
+
raise StorePathNotRelativeError
|
|
63
|
+
|
|
64
|
+
self._store_path = value
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def home_directory(self) -> Path:
|
|
68
|
+
"""Set home directory."""
|
|
69
|
+
return Path.home()
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def username(self) -> str:
|
|
73
|
+
"""Set username."""
|
|
74
|
+
return getpass.getuser()
|
|
75
|
+
|
|
76
|
+
@cached_property
|
|
77
|
+
def archive_path(self) -> str:
|
|
78
|
+
"""Set archive path."""
|
|
79
|
+
user_directory = os.path.join(
|
|
80
|
+
self.archive_path_root_directory,
|
|
81
|
+
self.username,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
os.makedirs(user_directory, exist_ok=True)
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
os.path.join(
|
|
88
|
+
user_directory,
|
|
89
|
+
f"archive-{generate_random_string().lower()}",
|
|
90
|
+
)
|
|
91
|
+
+ "."
|
|
92
|
+
+ self.EXTENSION_FILE_TAR_GZ
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def create(self) -> Tuple[str, str]:
|
|
96
|
+
"""Create archive of store path."""
|
|
97
|
+
|
|
98
|
+
# Create file with correct permissions
|
|
99
|
+
|
|
100
|
+
with open(self.archive_path, "w"):
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
os.chmod(self.archive_path, 0o600)
|
|
104
|
+
|
|
105
|
+
# Create archive
|
|
106
|
+
|
|
107
|
+
with tarfile.open(self.archive_path, "w:gz") as tar:
|
|
108
|
+
tar.add(
|
|
109
|
+
os.path.join(self.home_directory, self.store_path),
|
|
110
|
+
filter=lambda x: None if x.name in self.exclude_paths else x,
|
|
111
|
+
# self.store_path is relative to home directory, so this makes
|
|
112
|
+
# paths inside the archive relative to home directory
|
|
113
|
+
arcname=self.store_path,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return self.archive_path, get_md5_hash(self.archive_path)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class UNIXUserHomeDirectoryArchiveRestoration:
|
|
120
|
+
"""Abstraction of UNIX user home directory archive restore process.
|
|
121
|
+
|
|
122
|
+
Restores path in archive to path on local filesystem.
|
|
123
|
+
|
|
124
|
+
This function extracts the given path in the archive to a temporary directory.
|
|
125
|
+
It then replaces the path on the local filesystem with the temporary directory.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
# Ensure all restore-related directories start with this prefix. The prefix
|
|
129
|
+
# might be used by other systems to recognise directories that are related
|
|
130
|
+
# to an archive restore.
|
|
131
|
+
|
|
132
|
+
PREFIX_RESTORE_FILESYSTEM_OBJECT = ".archive-restore-"
|
|
133
|
+
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
*,
|
|
137
|
+
store_path: str,
|
|
138
|
+
filesystem_path: str,
|
|
139
|
+
archive_path: str,
|
|
140
|
+
temporary_path_root_path: str,
|
|
141
|
+
exclude_paths: Optional[List[str]] = None,
|
|
142
|
+
):
|
|
143
|
+
"""Set attributes."""
|
|
144
|
+
self.archive_path = archive_path
|
|
145
|
+
self.store_path = store_path
|
|
146
|
+
self.filesystem_path = filesystem_path
|
|
147
|
+
self.exclude_paths = exclude_paths if exclude_paths is not None else []
|
|
148
|
+
self.temporary_path_root_path = temporary_path_root_path
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def filesystem_path(self) -> str:
|
|
152
|
+
"""Set filesystem path.
|
|
153
|
+
|
|
154
|
+
Path on local filesystem is absolute, so is path with leading slash.
|
|
155
|
+
"""
|
|
156
|
+
return self._filesystem_path
|
|
157
|
+
|
|
158
|
+
@filesystem_path.setter
|
|
159
|
+
def filesystem_path(self, value: str) -> None:
|
|
160
|
+
"""Set filesystem path.
|
|
161
|
+
|
|
162
|
+
Path on local filesystem is absolute, so is path with leading slash.
|
|
163
|
+
"""
|
|
164
|
+
if os.path.isabs(value):
|
|
165
|
+
raise FilesystemPathNotRelativeError
|
|
166
|
+
|
|
167
|
+
self._filesystem_path = os.path.join(self.home_directory, value)
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def store_path(self) -> str:
|
|
171
|
+
"""Set store path.
|
|
172
|
+
|
|
173
|
+
Path in archive is relative, so is path without leading slash.
|
|
174
|
+
"""
|
|
175
|
+
return self._store_path
|
|
176
|
+
|
|
177
|
+
@store_path.setter
|
|
178
|
+
def store_path(self, value: str) -> None:
|
|
179
|
+
"""Set store path.
|
|
180
|
+
|
|
181
|
+
Checks if store path is relative.
|
|
182
|
+
"""
|
|
183
|
+
if os.path.isabs(value):
|
|
184
|
+
raise StorePathNotRelativeError
|
|
185
|
+
|
|
186
|
+
self._store_path = value
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def home_directory(self) -> Path:
|
|
190
|
+
"""Set home directory."""
|
|
191
|
+
return Path.home()
|
|
192
|
+
|
|
193
|
+
@cached_property
|
|
194
|
+
def temporary_path(
|
|
195
|
+
self,
|
|
196
|
+
) -> str:
|
|
197
|
+
"""Generate and create temporary path."""
|
|
198
|
+
temporary_path = os.path.join(
|
|
199
|
+
self.temporary_path_root_path,
|
|
200
|
+
self.PREFIX_RESTORE_FILESYSTEM_OBJECT
|
|
201
|
+
+ "tmp."
|
|
202
|
+
+ os.path.basename(self.filesystem_path)
|
|
203
|
+
+ "-"
|
|
204
|
+
+ generate_random_string(8),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
os.mkdir(temporary_path)
|
|
208
|
+
os.chmod(temporary_path, 0o700)
|
|
209
|
+
|
|
210
|
+
return temporary_path
|
|
211
|
+
|
|
212
|
+
@cached_property
|
|
213
|
+
def old_path(self) -> str:
|
|
214
|
+
"""Set old path."""
|
|
215
|
+
|
|
216
|
+
# Add dot prefix to prevent access, and add random string in case filesystem
|
|
217
|
+
# object without random string already exists
|
|
218
|
+
|
|
219
|
+
return os.path.join(
|
|
220
|
+
Path(self.filesystem_path).parent,
|
|
221
|
+
self.PREFIX_RESTORE_FILESYSTEM_OBJECT
|
|
222
|
+
+ "old."
|
|
223
|
+
+ os.path.basename(self.filesystem_path)
|
|
224
|
+
+ "-"
|
|
225
|
+
+ generate_random_string(8),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
@cached_property
|
|
229
|
+
def new_path(self) -> str:
|
|
230
|
+
"""Set new path."""
|
|
231
|
+
|
|
232
|
+
# Add dot prefix to prevent access, and add random string in case filesystem
|
|
233
|
+
# object without random string already exists
|
|
234
|
+
|
|
235
|
+
return os.path.join(
|
|
236
|
+
Path(self.filesystem_path).parent,
|
|
237
|
+
self.PREFIX_RESTORE_FILESYSTEM_OBJECT
|
|
238
|
+
+ "new."
|
|
239
|
+
+ os.path.basename(self.filesystem_path)
|
|
240
|
+
+ "-"
|
|
241
|
+
+ generate_random_string(8),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def _extract(self) -> None:
|
|
245
|
+
"""Extract store path to temporary path."""
|
|
246
|
+
tar = tarfile.open(self.archive_path)
|
|
247
|
+
|
|
248
|
+
# As the entire archive is extracted by tar.extractall, we must check
|
|
249
|
+
# here that the archive contains only expected members, i.e. those in
|
|
250
|
+
# the store path. If the archive contains any member that is not in
|
|
251
|
+
# the given store path, an exception will be raised instead of silently
|
|
252
|
+
# continuing because, when creating the archive with UNIXUserHomeDirectoryArchive,
|
|
253
|
+
# this should never happen.
|
|
254
|
+
|
|
255
|
+
for member in tar.getmembers():
|
|
256
|
+
# Member *is* the store path itself, which is the only member
|
|
257
|
+
# allowed outside of the store path
|
|
258
|
+
|
|
259
|
+
if member.name == self.store_path:
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
# Member is inside the given store path
|
|
263
|
+
|
|
264
|
+
if Path(self.store_path) in PosixPath(member.name).parents:
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
raise IllegalMemberError(member.name)
|
|
268
|
+
|
|
269
|
+
# Extract entire archive
|
|
270
|
+
|
|
271
|
+
tar.extractall(self.temporary_path)
|
|
272
|
+
|
|
273
|
+
tar.close()
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def username(self) -> str:
|
|
277
|
+
"""Set username."""
|
|
278
|
+
return getpass.getuser()
|
|
279
|
+
|
|
280
|
+
def _copy(self) -> None:
|
|
281
|
+
"""Copy files that are in the filesystem path but not in the archive (i.e. removed) and are excluded (excluding excludes from deletion)."""
|
|
282
|
+
for f in get_files_only_in_right_directory(
|
|
283
|
+
os.path.join(self.temporary_path, self.store_path),
|
|
284
|
+
self.filesystem_path,
|
|
285
|
+
):
|
|
286
|
+
if (
|
|
287
|
+
os.path.relpath(f, self.home_directory)
|
|
288
|
+
not in self.exclude_paths
|
|
289
|
+
):
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
destination_path = os.path.join(
|
|
293
|
+
self.temporary_path,
|
|
294
|
+
self.store_path,
|
|
295
|
+
os.path.relpath(f, self.filesystem_path),
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
shutil.copyfile(f, destination_path)
|
|
299
|
+
|
|
300
|
+
for f in get_directories_only_in_right_directory(
|
|
301
|
+
os.path.join(self.temporary_path, self.store_path),
|
|
302
|
+
self.filesystem_path,
|
|
303
|
+
):
|
|
304
|
+
if (
|
|
305
|
+
os.path.relpath(f, self.home_directory)
|
|
306
|
+
not in self.exclude_paths
|
|
307
|
+
):
|
|
308
|
+
continue
|
|
309
|
+
|
|
310
|
+
destination_path = os.path.join(
|
|
311
|
+
self.temporary_path,
|
|
312
|
+
self.store_path,
|
|
313
|
+
os.path.relpath(f, self.filesystem_path),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
shutil.copytree(f, destination_path, ignore_dangling_symlinks=True)
|
|
317
|
+
|
|
318
|
+
def replace(self) -> None:
|
|
319
|
+
"""Replace object on local filesystem with object from archive.
|
|
320
|
+
|
|
321
|
+
This is a nearly atomic process. I.e. there is almost no downtime when
|
|
322
|
+
replacing.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
# Extract archive. The filesystem path remains untouched until this is
|
|
326
|
+
# completed. This ensures that the filesystem is not left in a broken
|
|
327
|
+
# state if the extraction fails.
|
|
328
|
+
|
|
329
|
+
self._extract()
|
|
330
|
+
|
|
331
|
+
# Copy deleted files that were excluded
|
|
332
|
+
|
|
333
|
+
if os.path.lexists(self.filesystem_path):
|
|
334
|
+
self._copy()
|
|
335
|
+
|
|
336
|
+
# Move the extracted filesystem objects to the new path. As these may be
|
|
337
|
+
# on different filesystems, the move could take a while. We restore to the
|
|
338
|
+
# new path instead of to the filesystem path. If we restored to the filesystem
|
|
339
|
+
# path, we would have to get the original filesystem object out of the way,
|
|
340
|
+
# leaving the filesystem structure in a 'broken' state, while the move could
|
|
341
|
+
# take a while. In order to prevent downtime, we restore to this temporary
|
|
342
|
+
# new directory first.
|
|
343
|
+
|
|
344
|
+
shutil.move(
|
|
345
|
+
os.path.join(self.temporary_path, self.store_path),
|
|
346
|
+
self.new_path,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# If the filesystem path already exists, move it out of the way so that
|
|
350
|
+
# we can move the new path to it. This procedure is also followed for
|
|
351
|
+
# regular files. Unlike non-empty directories, regular files can be
|
|
352
|
+
# overwritten without having to ensure the filesystem object does not
|
|
353
|
+
# exist at the path, unless the regular file is write-protected (e.g.
|
|
354
|
+
# if it has permissions 0400).
|
|
355
|
+
|
|
356
|
+
if os.path.lexists(self.filesystem_path):
|
|
357
|
+
os.rename(self.filesystem_path, self.old_path)
|
|
358
|
+
|
|
359
|
+
# Move the new path to the filesystem path. This completes the restore.
|
|
360
|
+
|
|
361
|
+
os.rename(self.new_path, self.filesystem_path)
|
|
362
|
+
|
|
363
|
+
# Remove the old path if it exists (it exists if the filesystem path
|
|
364
|
+
# existed before doing the restore, see above).
|
|
365
|
+
|
|
366
|
+
if os.path.lexists(self.old_path):
|
|
367
|
+
shutil.rmtree(self.old_path)
|
python3-cyberfusion-sync-support-1.2.7.3.1/src/python3_cyberfusion_sync_support.egg-info/PKG-INFO
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python3-cyberfusion-sync-support
|
|
3
|
+
Version: 1.2.7.3.1
|
|
4
|
+
Summary: Library for syncing objects (e.g. directories).
|
|
5
|
+
Home-page: https://github.com/CyberfusionIO/python3-cyberfusion-sync-support
|
|
6
|
+
Author: Cyberfusion
|
|
7
|
+
Author-email: support@cyberfusion.io
|
|
8
|
+
Platform: linux
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# python3-cyberfusion-sync-support
|
|
12
|
+
|
|
13
|
+
Library for syncing objects (e.g. directories).
|
|
14
|
+
|
|
15
|
+
# Install
|
|
16
|
+
|
|
17
|
+
## PyPI
|
|
18
|
+
|
|
19
|
+
Run the following command to install the package from PyPI:
|
|
20
|
+
|
|
21
|
+
pip3 install python3-cyberfusion-sync-support
|
|
22
|
+
|
|
23
|
+
## Generic
|
|
24
|
+
|
|
25
|
+
Run the following command to create a source distribution:
|
|
26
|
+
|
|
27
|
+
python3 setup.py sdist
|
|
28
|
+
|
|
29
|
+
## Debian
|
|
30
|
+
|
|
31
|
+
Run the following commands to build a Debian package:
|
|
32
|
+
|
|
33
|
+
mk-build-deps -i -t 'apt -o Debug::pkgProblemResolver=yes --no-install-recommends -y'
|
|
34
|
+
dpkg-buildpackage -us -uc
|
|
35
|
+
|
|
36
|
+
# Configure
|
|
37
|
+
|
|
38
|
+
No configuration is supported.
|
|
39
|
+
|
|
40
|
+
# Usage
|
|
41
|
+
|
|
42
|
+
See code.
|
python3-cyberfusion-sync-support-1.2.7.3.1/src/python3_cyberfusion_sync_support.egg-info/SOURCES.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.cfg
|
|
4
|
+
setup.py
|
|
5
|
+
src/cyberfusion/SyncSupport/__init__.py
|
|
6
|
+
src/cyberfusion/SyncSupport/exceptions.py
|
|
7
|
+
src/cyberfusion/SyncSupport/unix_users.py
|
|
8
|
+
src/python3_cyberfusion_sync_support.egg-info/PKG-INFO
|
|
9
|
+
src/python3_cyberfusion_sync_support.egg-info/SOURCES.txt
|
|
10
|
+
src/python3_cyberfusion_sync_support.egg-info/dependency_links.txt
|
|
11
|
+
src/python3_cyberfusion_sync_support.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cyberfusion
|