mirror of https://github.com/borgbase/vorta
Merge branch 'borgbase:master' into master
This commit is contained in:
commit
e6faa54dee
|
@ -1,5 +1,5 @@
|
|||
name: "Bug Report Form"
|
||||
description: "Report a bug or a similiar issue."
|
||||
description: "Report a bug or a similar issue."
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: Bug Report
|
||||
about: Report a bug or a similiar issue - the classic way
|
||||
about: Report a bug or a similar issue - the classic way
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
@ -18,7 +18,7 @@ If you want to suggest a feature or have any other question, please use our
|
|||
#### Description
|
||||
|
||||
<!-- Description
|
||||
Please decribe your issue and its context in a clear and concise way.
|
||||
Please describe your issue and its context in a clear and concise way.
|
||||
Please try to reproduce the issue and provide the steps to reproduce it.
|
||||
-->
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
- [ ] All new and existing tests passed.
|
||||
|
||||
|
||||
*I provide my contribution under the terms of the [license](./../../LICENSE.txt) of this repository and I affirm the [Developer Certificate of Origin][dco].*
|
||||
*I provide my contribution under the terms of the [license](./../LICENSE.txt) of this repository and I affirm the [Developer Certificate of Origin][dco].*
|
||||
|
||||
[dco]: https://developercertificate.org/
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
Vorta is a backup client for macOS and Linux desktops. It integrates the mighty [BorgBackup](https://borgbackup.readthedocs.io) with your desktop environment to protect your data from disk failure, ransomware and theft.
|
||||
|
||||
![](https://files.qmax.us/vorta/screencast-8-small.gif)
|
||||
https://github.com/m3nu/vorta/assets/3916435/a622a148-5373-4ae0-87bc-4ca1d6f6202e
|
||||
|
||||
## Why is this great? 🤩
|
||||
|
||||
|
|
|
@ -28,10 +28,10 @@ else:
|
|||
@nox.parametrize("borgbackup", supported_borgbackup_versions)
|
||||
def run_tests(session, borgbackup):
|
||||
# install borgbackup
|
||||
if (sys.platform == 'darwin'):
|
||||
if sys.platform == 'darwin':
|
||||
# in macOS there's currently no fuse package which works with borgbackup directly
|
||||
session.install(f"borgbackup=={borgbackup}")
|
||||
elif (borgbackup == "1.1.18"):
|
||||
elif borgbackup == "1.1.18":
|
||||
# borgbackup 1.1.18 doesn't support pyfuse3
|
||||
session.install("llfuse")
|
||||
session.install(f"borgbackup[llfuse]=={borgbackup}")
|
||||
|
|
|
@ -18,10 +18,10 @@ def create_symlink(folder: Path) -> None:
|
|||
"""Create the appropriate symlink in the MacOS folder
|
||||
pointing to the Resources folder.
|
||||
"""
|
||||
sibbling = Path(str(folder).replace("MacOS", ""))
|
||||
sibling = Path(str(folder).replace("MacOS", ""))
|
||||
|
||||
# PyQt6/Qt/qml/QtQml/Models.2
|
||||
root = str(sibbling).partition("Contents")[2].lstrip("/")
|
||||
root = str(sibling).partition("Contents")[2].lstrip("/")
|
||||
# ../../../../
|
||||
backward = "../" * (root.count("/") + 1)
|
||||
# ../../../../Resources/PyQt6/Qt/qml/QtQml/Models.2
|
||||
|
@ -41,7 +41,7 @@ def fix_dll(dll: Path) -> None:
|
|||
|
||||
def match_func(pth: str) -> Optional[str]:
|
||||
"""Callback function for MachO.rewriteLoadCommands() that is
|
||||
called on every lookup path setted in the DLL headers.
|
||||
called on every lookup path set in the DLL headers.
|
||||
By returning None for system libraries, it changes nothing.
|
||||
Else we return a relative path pointing to the good file
|
||||
in the MacOS folder.
|
||||
|
@ -73,7 +73,7 @@ def find_problematic_folders(folder: Path) -> Generator[Path, None, None]:
|
|||
"""Recursively yields problematic folders (containing a dot in their name)."""
|
||||
for path in folder.iterdir():
|
||||
if not path.is_dir() or path.is_symlink():
|
||||
# Skip simlinks as they are allowed (even with a dot)
|
||||
# Skip symlinks as they are allowed (even with a dot)
|
||||
continue
|
||||
if "." in path.name:
|
||||
yield path
|
||||
|
@ -83,7 +83,7 @@ def find_problematic_folders(folder: Path) -> Generator[Path, None, None]:
|
|||
|
||||
def move_contents_to_resources(folder: Path) -> Generator[Path, None, None]:
|
||||
"""Recursively move any non symlink file from a problematic folder
|
||||
to the sibbling one in Resources.
|
||||
to the sibling one in Resources.
|
||||
"""
|
||||
for path in folder.iterdir():
|
||||
if path.is_symlink():
|
||||
|
@ -91,10 +91,10 @@ def move_contents_to_resources(folder: Path) -> Generator[Path, None, None]:
|
|||
if path.name == "qml":
|
||||
yield from move_contents_to_resources(path)
|
||||
else:
|
||||
sibbling = Path(str(path).replace("MacOS", "Resources"))
|
||||
sibbling.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(path, sibbling)
|
||||
yield sibbling
|
||||
sibling = Path(str(path).replace("MacOS", "Resources"))
|
||||
sibling.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(path, sibling)
|
||||
yield sibling
|
||||
|
||||
|
||||
def main(args: List[str]) -> int:
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = '0.9.1-beta3'
|
||||
__version__ = '0.9.1'
|
||||
|
|
|
@ -308,11 +308,6 @@ class VortaApp(QtSingleApplication):
|
|||
|
||||
Displays a `QMessageBox` with an error message depending on the
|
||||
return code of the `BorgJob`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
repo_url : str
|
||||
The url of the repo of concern
|
||||
"""
|
||||
# extract data from the params for the borg job
|
||||
repo_url = result['params']['repo_url']
|
||||
|
@ -344,7 +339,7 @@ class VortaApp(QtSingleApplication):
|
|||
elif returncode > 128:
|
||||
# 128+N - killed by signal N (e.g. 137 == kill -9)
|
||||
signal = returncode - 128
|
||||
text = self.tr('Repository data check for repo was killed by signal %s.') % (signal)
|
||||
text = self.tr('Repository data check for repo was killed by signal %s.') % signal
|
||||
infotext = self.tr('The process running the check job got a kill signal. Try again.')
|
||||
else:
|
||||
# Real error
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#000000" d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 753 B After Width: | Height: | Size: 768 B |
|
@ -49,6 +49,13 @@
|
|||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="v0.9.1" date="2024-01-10" urgency="low">
|
||||
<description>
|
||||
<ul>
|
||||
<li>First production 0.9 release</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="v0.9.1-beta2" date="2023-10-27" urgency="low">
|
||||
<description>
|
||||
<ul>
|
||||
|
|
|
@ -25,9 +25,9 @@ class JobInterface(QObject):
|
|||
@abstractmethod
|
||||
def cancel(self):
|
||||
"""
|
||||
Cancel can be called when the job is not started. It is the responsability of FuncJob to not cancel job if
|
||||
Cancel can be called when the job is not started. It is the responsibility of FuncJob to not cancel job if
|
||||
no job is running.
|
||||
The cancel mehod of JobsManager calls the cancel method on the running jobs only. Other jobs are dequeued.
|
||||
The cancel method of JobsManager calls the cancel method on the running jobs only. Other jobs are dequeued.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -50,6 +50,7 @@ class SiteWorker(threading.Thread):
|
|||
self.current_job = None
|
||||
|
||||
def run(self):
|
||||
job = None
|
||||
while True:
|
||||
try:
|
||||
job = self.jobs.get(False)
|
||||
|
@ -58,7 +59,8 @@ class SiteWorker(threading.Thread):
|
|||
job.run()
|
||||
logger.debug("Finish job for site: %s", job.repo_id())
|
||||
except queue.Empty:
|
||||
logger.debug("No more jobs for site: %s", job.repo_id())
|
||||
if job is not None:
|
||||
logger.debug("No more jobs for site: %s", job.repo_id())
|
||||
return
|
||||
|
||||
|
||||
|
@ -77,19 +79,20 @@ class JobsManager:
|
|||
|
||||
def is_worker_running(self, site=None):
|
||||
"""
|
||||
See if there are any active jobs. The user can't start a backup if a job is
|
||||
running. The scheduler can.
|
||||
"""
|
||||
# Check status for specific site (repo)
|
||||
if site in self.workers:
|
||||
return self.workers[site].is_alive()
|
||||
else:
|
||||
return False
|
||||
See if there are any active jobs.
|
||||
The user can't start a backup if a job is running. The scheduler can.
|
||||
|
||||
# Check if *any* worker is active
|
||||
for _, worker in self.workers.items():
|
||||
if worker.is_alive():
|
||||
return True
|
||||
If site is None, check if there is any worker active for any site (repo).
|
||||
If site is not None, only check if there is a worker active for the given site (repo).
|
||||
"""
|
||||
if site is not None:
|
||||
if site in self.workers:
|
||||
if self.workers[site].is_alive():
|
||||
return True
|
||||
else:
|
||||
for _, worker in self.workers.items():
|
||||
if worker.is_alive():
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_job(self, job):
|
||||
|
|
|
@ -24,7 +24,7 @@ class NetworkStatusMonitor:
|
|||
|
||||
def is_network_status_available(self):
|
||||
"""Is the network status really available, and not just a dummy implementation?"""
|
||||
return type(self) != NetworkStatusMonitor
|
||||
return type(self) is not NetworkStatusMonitor
|
||||
|
||||
def is_network_metered(self) -> bool:
|
||||
"""Is the currently connected network a metered connection?"""
|
||||
|
|
|
@ -36,7 +36,7 @@ class ProfileExport:
|
|||
def repo_url(self):
|
||||
if (
|
||||
'repo' in self._profile_dict
|
||||
and type(self._profile_dict['repo']) == dict
|
||||
and isinstance(self._profile_dict['repo'], dict)
|
||||
and 'url' in self._profile_dict['repo']
|
||||
):
|
||||
return self._profile_dict['repo']['url']
|
||||
|
|
|
@ -70,7 +70,7 @@ class VortaScheduler(QtCore.QObject):
|
|||
self.bus = bus
|
||||
self.bus.connect(service, path, interface, name, "b", self.loginSuspendNotify)
|
||||
else:
|
||||
logger.warn('Failed to connect to DBUS interface to detect sleep/resume events')
|
||||
logger.warning('Failed to connect to DBUS interface to detect sleep/resume events')
|
||||
|
||||
@QtCore.pyqtSlot(bool)
|
||||
def loginSuspendNotify(self, suspend: bool):
|
||||
|
|
|
@ -875,7 +875,7 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin):
|
|||
return msg.exec() == QMessageBox.StandardButton.Yes
|
||||
|
||||
def delete_action(self):
|
||||
# Since this function modify the UI, we can't put the whole function in a JobQUeue.
|
||||
# Since this function modify the UI, we can't put the whole function in a JobQueue.
|
||||
|
||||
# determine selected archives
|
||||
archives = []
|
||||
|
|
|
@ -381,7 +381,6 @@ def parse_diff_lines(lines: List[str], model: 'DiffTree'):
|
|||
|
||||
if not parsed_line:
|
||||
raise Exception("Couldn't parse diff output `{}`".format(line))
|
||||
continue
|
||||
|
||||
path = PurePath(parsed_line['path'])
|
||||
file_type = FileType.FILE
|
||||
|
|
|
@ -610,7 +610,7 @@ class FileTreeModel(QAbstractItemModel, Generic[T]):
|
|||
if isinstance(path, PurePath):
|
||||
path = path.parts
|
||||
|
||||
return self.root.get_path(path) # handels empty path
|
||||
return self.root.get_path(path) # handles empty path
|
||||
|
||||
def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):
|
||||
"""
|
||||
|
|
|
@ -27,7 +27,7 @@ class AddProfileWindow(AddProfileBase, AddProfileUI):
|
|||
|
||||
self.name_blank = trans_late('AddProfileWindow', 'Please enter a profile name.')
|
||||
self.name_exists = trans_late('AddProfileWindow', 'A profile with this name already exists.')
|
||||
# Call validate to set inital messages
|
||||
# Call validate to set initial messages
|
||||
self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setEnabled(self.validate())
|
||||
|
||||
def _set_status(self, text):
|
||||
|
|
|
@ -40,7 +40,7 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
|
|||
# compression or speed on a unified scale. this is not 1-dimensional and also depends
|
||||
# on the input data. so we just tell what we know for sure.
|
||||
# "auto" is used for some slower / older algorithms to avoid wasting a lot of time
|
||||
# on uncompressible data.
|
||||
# on incompressible data.
|
||||
self.repoCompression.addItem(self.tr('LZ4 (modern, default)'), 'lz4')
|
||||
self.repoCompression.addItem(self.tr('Zstandard Level 3 (modern)'), 'zstd,3')
|
||||
self.repoCompression.addItem(self.tr('Zstandard Level 8 (modern)'), 'zstd,8')
|
||||
|
|
|
@ -331,7 +331,7 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin):
|
|||
profile = self.profile()
|
||||
# sort indexes, starting with lowest
|
||||
indexes.sort()
|
||||
# remove each selected row, starting with highest index (otherways, higher indexes become invalid)
|
||||
# remove each selected row, starting with the highest index (otherwise, higher indexes become invalid)
|
||||
for index in reversed(indexes):
|
||||
db_item = SourceFileModel.get(
|
||||
dir=self.sourceFilesWidget.item(index.row(), SourceColumn.Path).text(),
|
||||
|
|
|
@ -87,7 +87,7 @@ class TestFileSystemItem:
|
|||
item.add(child2)
|
||||
item.add(child3)
|
||||
|
||||
# test get inexistent subpath
|
||||
# test get nonexistent subpath
|
||||
assert item.get('unknown') is None
|
||||
assert item.get('unknown', default='default') == 'default'
|
||||
|
||||
|
|
Loading…
Reference in New Issue