mirror of https://gitlab.com/KKlochko/tui-rsync
Compare commits
No commits in common. '10114da0eebaf2e925b73d5f334feeb5f56fd43d' and 'f2e70ccc85fec6299a6fad734331defbb5deebcc' have entirely different histories.
10114da0ee
...
f2e70ccc85
@ -1,181 +0,0 @@
|
||||
pyrightconfig.json
|
||||
*~
|
||||
.\#*
|
||||
\#*\#
|
||||
.*.~undo-tree~
|
||||
*.db
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/python
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### Python Patch ###
|
||||
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||
poetry.toml
|
||||
|
||||
# ruff
|
||||
.ruff_cache/
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python
|
||||
|
@ -1,28 +0,0 @@
|
||||
Feature: Creating a backup plan with the CLI
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.cli
|
||||
Scenario Outline: Create an new unique backup plan with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "success"
|
||||
|
||||
Examples:
|
||||
| arguments |
|
||||
| plans add --label my_label --source /mnt -d /mnt2 -d /mnt3 |
|
||||
| plans add --label label2 --source /mnt -d /mnt2 -d /mnt3 |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of creating an new unique backup plan with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI output contains "<result_message>"
|
||||
|
||||
Examples:
|
||||
| arguments | result | result_message | description |
|
||||
| plans add --label my_label --source /mnt -d /mnt2 -d /mnt3 | success | The backup plan is added. | add a plan |
|
||||
|
@ -1,72 +0,0 @@
|
||||
Feature: Deleting a backup plan with the CLI
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
@fixture.cli
|
||||
Scenario Outline: Deleting a backup plan with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
And I have a backup plan with id="<existing_backup_plan_id>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
|
||||
Examples:
|
||||
| arguments | existing_backup_plan_id | result | description |
|
||||
| plans remove one -i 8aa59e7e-dc75-459b-beb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be583 | success | delete an existing plan |
|
||||
| plans remove one -i 8aa59e7e-dc75-459b-aeb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be512 | error | delete a non-existing plan |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of deleting non-existing backup plan with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI contains the error: "<result_error>"
|
||||
|
||||
Examples:
|
||||
| arguments | result | result_error | description |
|
||||
| plans remove one -i 8aa59e7e-dc75-459b-aeb5-b710b39be583 | error | [ERROR] Failed to delete the backup plan, because it doesn't exist. | delete a non-existing plan |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of deleting existing backup plans with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
And I have a backup plan with id="<existing_backup_plan_id>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI output contains "<result_message>"
|
||||
And the CLI output contains "<existing_backup_plan_id>"
|
||||
|
||||
Examples:
|
||||
| arguments | existing_backup_plan_id | result | result_message | description |
|
||||
| plans remove one -i 8aa59e7e-dc75-459b-aeb5-b710b39be583 | 8aa59e7e-dc75-459b-aeb5-b710b39be583 | success | Removed the backup plan with | delete the backup plan |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of deleting no backup plans with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI output contains "<result_message>"
|
||||
|
||||
Examples:
|
||||
| arguments | result | result_message | description |
|
||||
| plans remove all | success | Nothing to remove. No backup plans. | delete no plans |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of deleting all backup plans with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI output contains "<result_message>"
|
||||
|
||||
Examples:
|
||||
| arguments | result | result_message | description |
|
||||
| plans remove all | success | All backup plans removed. | delete all backup plans |
|
@ -1,75 +0,0 @@
|
||||
Feature: Show a backup plan with the CLI
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
@fixture.cli
|
||||
Scenario Outline: Show a backup plan with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
And I have a backup plan with id="<existing_backup_plan_id>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
|
||||
Examples:
|
||||
| arguments | existing_backup_plan_id | result | description |
|
||||
| plans show one -i 8aa59e7e-dc75-459b-beb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be583 | success | show an existing plan |
|
||||
| plans show one -i 8aa59e7e-dc75-459b-aeb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be512 | error | show a non-existing plan |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of showing existing backup plans with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
And I have a backup plan with id="<existing_backup_plan_id>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI output doesn't contains "<result_message>"
|
||||
And the CLI output contains "<existing_backup_plan_id>"
|
||||
|
||||
Examples:
|
||||
| arguments | existing_backup_plan_id | result | result_message | description |
|
||||
| plans show one -i 8aa59e7e-dc75-459b-aeb5-b710b39be583 | 8aa59e7e-dc75-459b-aeb5-b710b39be583 | success | Removed the backup plan with | delete the backup plan |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of showing non-existing backup plans with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI contains the error: "<result_error>"
|
||||
|
||||
Examples:
|
||||
| arguments | result | result_error | description |
|
||||
| plans show one -i 8aa59e7e-dc75-459b-aeb5-b710b39be583 | error | [ERROR] The backup plan was not found. | show a non-existing plan |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of showing no backup plans with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI output contains "<result_message>"
|
||||
|
||||
Examples:
|
||||
| arguments | result | result_message | description |
|
||||
| plans show all | success | No backup plans. | shows no plans |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of showing backup plans with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
And I have a backup plan with id="8aa59e7e-dc75-459b-beb5-b710b39be583"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI output contains "8aa59e7e-dc75-459b-beb5-b710b39be583"
|
||||
And the CLI output doesn't contains "<no_message>"
|
||||
|
||||
Examples:
|
||||
| arguments | result | no_message | description |
|
||||
| plans show all | success | No backup plans. | shows plans |
|
||||
|
@ -1,45 +0,0 @@
|
||||
Feature: Update a backup plan with the CLI
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
@fixture.cli
|
||||
Scenario Outline: Replace a backup plan with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
And I have a backup plan with id="<existing_backup_plan_id>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
|
||||
Examples:
|
||||
| arguments | existing_backup_plan_id | result | description |
|
||||
| plans update replace -i 8aa59e7e-dc75-459b-beb5-b710b39be583 -l test -s /mnt -d /mnt2 -d /mnt3 | 8aa59e7e-dc75-459b-beb5-b710b39be583 | success | replace an existing plan |
|
||||
| plans update replace -i 8aa59e7e-dc75-459b-beb5-b710b39be583 -l test -s /mnt -d /mnt2 -d /mnt3 | 8aa59e7e-dc75-459b-beb5-b710b39be512 | error | replace a non-existing plan |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of replacing non-existing backup plans with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI contains the error: "<result_error>"
|
||||
|
||||
Examples:
|
||||
| arguments | result | result_error | description |
|
||||
| plans update replace -i 8aa59e7e-dc75-459b-beb5-b710b39be583 -l test -s /mnt -d /mnt2 -d /mnt3 | error | [ERROR] The backup plan was not found. | replace a non-existing plan |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
@fixture.cli
|
||||
Scenario Outline: Response of replacing an existing backup plans with the CLI
|
||||
Given the CLI arguments are "<arguments>"
|
||||
And I have a backup plan with id="<existing_backup_plan_id>"
|
||||
When I run the CLI
|
||||
Then the CLI executed with "<result>"
|
||||
And the CLI output contains "<result_message>"
|
||||
|
||||
Examples:
|
||||
| arguments | existing_backup_plan_id | result | result_message | description |
|
||||
| plans update replace -i 8aa59e7e-dc75-459b-beb5-b710b39be583 -l test -s /mnt -d /mnt2 -d /mnt3 | 8aa59e7e-dc75-459b-beb5-b710b39be583 | success | The backup plan updated. | replace an existing plan |
|
||||
|
@ -1,17 +0,0 @@
|
||||
Feature: Creating a backup plan
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.backup_plan_service
|
||||
Scenario Outline: Create an new unique backup plan
|
||||
Given the label "<label>"
|
||||
And the path "<source_path>"
|
||||
And the destinations <destinations>
|
||||
When I create the backup plan
|
||||
Then it should be created successfully
|
||||
|
||||
Examples:
|
||||
| label | source_path | destinations |
|
||||
| usb | /mnt/usb | [] |
|
||||
| db | /db | ["/backup/db"] |
|
||||
| temp | /tmp | ["/backup/tmp1", "/backup/tmp2"] |
|
@ -1,28 +0,0 @@
|
||||
Feature: Delete a backup plan
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
Scenario Outline: Deleting a backup plan
|
||||
Given I have a backup plan with id="<existing_backup_plan_id>"
|
||||
When I remove the backup plan with id="<backup_plan_id>"
|
||||
Then the result should be "<result>"
|
||||
|
||||
Examples:
|
||||
| backup_plan_id | existing_backup_plan_id | result | description |
|
||||
| 8aa59e7e-dc75-459b-beb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be583 | success | delete an existing plan |
|
||||
| 8aa59e7e-dc75-459b-aeb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be512 | error | delete a non-existing plan |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
Scenario: Delete no backup plans
|
||||
When I remove all backup plans
|
||||
Then the result value should be "False"
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
Scenario: Delete all backup plans
|
||||
Given I have a backup plan with id="8aa59e7e-dc75-459b-beb5-b710b39be583"
|
||||
When I remove all backup plans
|
||||
Then the result value should be "True"
|
@ -1,14 +0,0 @@
|
||||
Feature: Read a backup plan by an id
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
Scenario Outline: Read a backup plan with the CLI
|
||||
Given I have a backup plan with id="<existing_backup_plan_id>"
|
||||
When I read the backup plan with id="<backup_plan_id>"
|
||||
Then the result should be "<result>"
|
||||
|
||||
Examples:
|
||||
| backup_plan_id | existing_backup_plan_id | result | description |
|
||||
| 8aa59e7e-dc75-459b-beb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be583 | success | delete an existing plan |
|
||||
| 8aa59e7e-dc75-459b-aeb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be512 | error | delete a non-existing plan |
|
@ -1,30 +0,0 @@
|
||||
Feature: Update a backup plan
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
Scenario Outline: Update a backup plan's fields
|
||||
Given I have a backup plan with id="<existing_backup_plan_id>"
|
||||
When I update the backup plan with id="<backup_plan_id>" and data="<updated_data>"
|
||||
Then the result should be "<result>"
|
||||
|
||||
Examples:
|
||||
| backup_plan_id | existing_backup_plan_id | updated_data | result | description |
|
||||
| 8aa59e7e-dc75-459b-beb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be583 | {"label": "new_label"} | success | update an existing plan |
|
||||
| 8aa59e7e-dc75-459b-aeb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be512 | {"label": "new_label"} | error | update a non-existing plan |
|
||||
|
||||
@fixture.injector
|
||||
@fixture.in_memory_database
|
||||
@fixture.seeds
|
||||
Scenario Outline: Updated backup plan's fields must be updated
|
||||
Given I have a backup plan with id="<existing_backup_plan_id>"
|
||||
When I update the backup plan with id="<backup_plan_id>" and data="<updated_data>"
|
||||
And I read the backup plan with id="<backup_plan_id>"
|
||||
Then the result should be "<result>"
|
||||
And the backup_plan must have same field: "<updated_data>"
|
||||
|
||||
Examples:
|
||||
| backup_plan_id | existing_backup_plan_id | updated_data | result | description |
|
||||
| 8aa59e7e-dc75-459b-beb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be583 | {"label": "new_label"} | success | update the label |
|
||||
| 8aa59e7e-dc75-459b-beb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be583 | {"source": "/new/mnt"} | success | update the source |
|
||||
| 8aa59e7e-dc75-459b-beb5-b710b39be583 | 8aa59e7e-dc75-459b-beb5-b710b39be583 | {"destinations": ["/mnt2", "/mnt3"]} | success | update the destinations |
|
@ -1,62 +0,0 @@
|
||||
from behave import fixture, use_fixture
|
||||
from injector import Injector
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from features.support import DataSeeds
|
||||
from tui_rsync.core.components.backup_plan.application.repository.backup_plan_repository import BackupPlanRepository
|
||||
from tui_rsync.core.components.backup_plan.application.services.backup_plan_service import BackupPlanService
|
||||
from tui_rsync.infrastructure.configuration import TestingConfiguration, CurrentConfiguration
|
||||
from tui_rsync.infrastructure.orm import InMemoryDatabaseManager
|
||||
from tui_rsync.user_interface.cli.cli import cli_app
|
||||
|
||||
|
||||
@fixture
|
||||
def setup_cli_runner(context):
|
||||
context.cli_runner = CliRunner()
|
||||
yield context.cli_runner
|
||||
|
||||
|
||||
@fixture
|
||||
def setup_cli_app(context):
|
||||
context.cli_app = cli_app
|
||||
yield context.cli_app
|
||||
|
||||
|
||||
@fixture
|
||||
def setup_in_memory_database_manager(context):
|
||||
context.db = context.injector.get(InMemoryDatabaseManager)
|
||||
yield context.db
|
||||
|
||||
|
||||
@fixture
|
||||
def setup_backup_plan_service(context):
|
||||
context.backup_plan_service = context.injector.get(BackupPlanService)
|
||||
yield context.backup_plan_service
|
||||
|
||||
|
||||
@fixture
|
||||
def setup_injector_for_testing(context):
|
||||
context.injector = Injector([TestingConfiguration()])
|
||||
CurrentConfiguration.set_injector(context.injector)
|
||||
yield context.injector
|
||||
|
||||
|
||||
@fixture
|
||||
def setup_seeds(context):
|
||||
context.data_seeds = DataSeeds(context.injector)
|
||||
context.data_seeds.seeds(context)
|
||||
yield context.data_seeds
|
||||
|
||||
|
||||
def before_tag(context, tag):
|
||||
if tag == "fixture.injector":
|
||||
use_fixture(setup_injector_for_testing, context)
|
||||
if tag == "fixture.in_memory_database":
|
||||
use_fixture(setup_in_memory_database_manager, context)
|
||||
if tag == "fixture.backup_plan_service":
|
||||
use_fixture(setup_backup_plan_service, context)
|
||||
if tag == "fixture.cli":
|
||||
use_fixture(setup_cli_app, context)
|
||||
use_fixture(setup_cli_runner, context)
|
||||
if tag == "fixture.seeds":
|
||||
use_fixture(setup_seeds, context)
|
@ -1,126 +0,0 @@
|
||||
from behave import given, when, then
|
||||
import json
|
||||
|
||||
from features.support.backup_plan_helpers import compare_destinations, update_backup_plan, compare_backup_plan_fields
|
||||
from features.support.helpers import json_to_dict
|
||||
from tui_rsync.core.components.backup_plan.application.services import RemoveAllBackupPlansService
|
||||
from tui_rsync.core.components.backup_plan.application.services.backup_plan_service import BackupPlanService
|
||||
from tui_rsync.core.components.backup_plan.domain import BackupPlan, Source, Destination
|
||||
from tui_rsync.core.shared_kernel.components.common import UUID
|
||||
from tui_rsync.core.shared_kernel.ports.Exceptions import CommandException
|
||||
from tui_rsync.core.shared_kernel.ports.Exceptions.query_exception import QueryException
|
||||
|
||||
|
||||
@given('the label "{label}"')
|
||||
def given_source_label(context, label):
|
||||
context.label = label
|
||||
|
||||
|
||||
@given('the path "{source_path}"')
|
||||
def given_source_path(context, source_path):
|
||||
context.source_path = source_path
|
||||
|
||||
|
||||
@given('the destinations {destinations_json}')
|
||||
def given_source_destinations(context, destinations_json):
|
||||
context.destinations = json.loads(destinations_json)
|
||||
|
||||
|
||||
@when('I create the backup plan')
|
||||
def add_backup_plan(context):
|
||||
try:
|
||||
context.backup_plan = BackupPlan(
|
||||
label=context.label,
|
||||
source=Source(context.source_path),
|
||||
destinations=list(map(lambda path: Destination(path), context.destinations)),
|
||||
)
|
||||
|
||||
context.backup_plan_service.add(context.backup_plan)
|
||||
|
||||
context.backup_plan = context.backup_plan_service.get_by_id(context.backup_plan.id)
|
||||
except Exception as e:
|
||||
context.exception_raised = True
|
||||
else:
|
||||
context.exception_raised = False
|
||||
|
||||
|
||||
@when('I update the backup plan with id="{backup_plan_id}" and data="{updated_data_json}"')
|
||||
def when_update_backup_plan_with_id(context, backup_plan_id, updated_data_json):
|
||||
try:
|
||||
context.backup_plan_service = context.injector.get(BackupPlanService)
|
||||
context.backup_plan = context.backup_plan_service.get_by_id(UUID(backup_plan_id))
|
||||
|
||||
update_backup_plan(context.backup_plan, json_to_dict(updated_data_json))
|
||||
|
||||
context.backup_plan_service.update(context.backup_plan)
|
||||
except QueryException as e:
|
||||
context.exception_raised = True
|
||||
else:
|
||||
context.exception_raised = False
|
||||
|
||||
|
||||
@when('I remove the backup plan with id="{backup_plan_id}"')
|
||||
def when_remove_backup_plan_with_id(context, backup_plan_id):
|
||||
try:
|
||||
context.backup_plan_service = context.injector.get(BackupPlanService)
|
||||
context.backup_plan_service.delete(UUID(backup_plan_id))
|
||||
except CommandException as e:
|
||||
context.exception_raised = True
|
||||
else:
|
||||
context.exception_raised = False
|
||||
|
||||
|
||||
@when('I remove all backup plans')
|
||||
def when_remove_all_backup_plans(context):
|
||||
try:
|
||||
context.backup_plan_service = context.injector.get(RemoveAllBackupPlansService)
|
||||
context.result_value = context.backup_plan_service.remove_all()
|
||||
except CommandException as e:
|
||||
context.exception_raised = True
|
||||
else:
|
||||
context.exception_raised = False
|
||||
|
||||
|
||||
@when('I read the backup plan with id="{backup_plan_id}"')
|
||||
def when_read_backup_plan_with_id(context, backup_plan_id):
|
||||
try:
|
||||
context.backup_plan_service = context.injector.get(BackupPlanService)
|
||||
context.backup_plan = context.backup_plan_service.get_by_id(UUID(backup_plan_id))
|
||||
except QueryException as e:
|
||||
context.exception_raised = True
|
||||
else:
|
||||
context.exception_raised = False
|
||||
|
||||
|
||||
@then('it should be created successfully')
|
||||
def backup_plan_has_added(context):
|
||||
assert context.exception_raised == False
|
||||
assert context.backup_plan is not None
|
||||
|
||||
assert context.backup_plan.label == context.label
|
||||
assert context.backup_plan.source == Source(context.source_path)
|
||||
|
||||
assert compare_destinations(
|
||||
context.backup_plan.destinations,
|
||||
context.destinations
|
||||
)
|
||||
|
||||
|
||||
@then('the result should be "{result}"')
|
||||
def then_cli_executed_successfully(context, result):
|
||||
match result:
|
||||
case "success":
|
||||
assert context.exception_raised == False
|
||||
case "error":
|
||||
assert context.exception_raised
|
||||
|
||||
|
||||
@then('the result value should be "{result}"')
|
||||
def then_cli_executed_successfully(context, result):
|
||||
assert result == str(context.result_value)
|
||||
|
||||
|
||||
@then('the backup_plan must have same field: "{fields_json}"')
|
||||
def backup_plan_has_added(context, fields_json):
|
||||
assert compare_backup_plan_fields(context.backup_plan, json_to_dict(fields_json))
|
||||
|
@ -1,39 +0,0 @@
|
||||
from behave import given, when, then
|
||||
|
||||
|
||||
@given('the CLI arguments are "{arguments}"')
|
||||
def given_cli_arguments(context, arguments):
|
||||
context.arguments = arguments.split()
|
||||
|
||||
|
||||
@when('I run the CLI')
|
||||
def when_run_cli(context):
|
||||
context.cli_result = context.cli_runner.invoke(context.cli_app, context.arguments)
|
||||
|
||||
|
||||
@then('the CLI executed with "{result}"')
|
||||
def then_cli_executed_successfully(context, result):
|
||||
match result:
|
||||
case "success":
|
||||
assert context.cli_result.exit_code == 0
|
||||
case "error":
|
||||
assert context.cli_result.exit_code != 0
|
||||
|
||||
|
||||
@then('the CLI output contains "{string}"')
|
||||
def then_cli_output_contains(context, string):
|
||||
print(f"Got: {context.cli_result.stdout}")
|
||||
assert string in context.cli_result.stdout
|
||||
|
||||
|
||||
@then('the CLI output doesn\'t contains "{string}"')
|
||||
def then_cli_output_contains(context, string):
|
||||
print(f"Got: {context.cli_result.stdout}")
|
||||
assert not (string in context.cli_result.stdout)
|
||||
|
||||
|
||||
@then('the CLI contains the error: "{string}"')
|
||||
def then_cli_output_contains_error(context, string):
|
||||
print(f"Got: {context.cli_result.stdout}")
|
||||
assert string in context.cli_result.stdout
|
||||
|
@ -1,7 +0,0 @@
|
||||
from behave import given
|
||||
|
||||
|
||||
@given('I have a backup plan with id="{existing_backup_plan_id}"')
|
||||
def given_existing_backup_plan_id_seed(context, existing_backup_plan_id):
|
||||
context.data_seeds.create_backup_plan(existing_backup_plan_id)
|
||||
|
@ -1,4 +0,0 @@
|
||||
from .fake_factories import FakeFactories
|
||||
from .data_seeds import DataSeeds
|
||||
|
||||
__all__ = ['FakeFactories', 'DataSeeds']
|
@ -1,57 +0,0 @@
|
||||
from features.support.helpers import json_to_dict
|
||||
from tui_rsync.core.components.backup_plan.domain import Destination, BackupPlan, Source
|
||||
|
||||
|
||||
def json_to_backup_plan(backup_plan_json):
|
||||
fields = json_to_dict(backup_plan_json)
|
||||
|
||||
backup_plan = BackupPlan(
|
||||
label=fields['label'],
|
||||
source=Source(fields['source']),
|
||||
destinations=list(map(lambda path: Destination(path), fields['destinations'])),
|
||||
)
|
||||
|
||||
return backup_plan
|
||||
|
||||
|
||||
def update_backup_plan(backup_plan, fields: dict):
|
||||
for field, value in fields.items():
|
||||
match field:
|
||||
case 'label':
|
||||
backup_plan.label = value
|
||||
case 'source':
|
||||
backup_plan.source = Source(value)
|
||||
case 'destinations':
|
||||
backup_plan.destinations = list(map(lambda path: Destination(path), value))
|
||||
|
||||
return backup_plan
|
||||
|
||||
|
||||
def compare_backup_plan_fields(backup_plan, fields: dict):
|
||||
for field, value in fields.items():
|
||||
match field:
|
||||
case 'label':
|
||||
if backup_plan.label != value:
|
||||
return False
|
||||
case 'source':
|
||||
if backup_plan.source.path != value:
|
||||
return False
|
||||
case 'destinations':
|
||||
if not compare_destinations(backup_plan.destinations, value):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def compare_destinations(actual: list[Destination], expected: list[str]) -> bool:
|
||||
actual_path_set = {destionation.path for destionation in actual}
|
||||
return actual_path_set == set(expected)
|
||||
|
||||
|
||||
def compare_backup_plans(backup_plan, expected):
|
||||
return backup_plan.label == expected.label \
|
||||
and backup_plan.source == expected.source_path \
|
||||
and compare_destinations(
|
||||
backup_plan.destinations,
|
||||
expected.destinations
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
from injector import Injector
|
||||
|
||||
from tui_rsync.core.components.backup_plan.application.services.backup_plan_service import BackupPlanService
|
||||
from . import FakeFactories
|
||||
|
||||
|
||||
class DataSeeds:
|
||||
def __init__(self, configuration: Injector):
|
||||
self.fake_factories = FakeFactories()
|
||||
self.configuration = configuration
|
||||
|
||||
def create_backup_plan(self, uuid: str | None = None):
|
||||
service = self.configuration.get(BackupPlanService)
|
||||
backup_plan = self.fake_factories.backup_plan(uuid)
|
||||
service.add(backup_plan)
|
||||
return backup_plan
|
||||
|
||||
def seeds(self, context):
|
||||
context.backup_plan_seed = self.create_backup_plan()
|
@ -1,34 +0,0 @@
|
||||
from faker import Faker
|
||||
|
||||
from tui_rsync.core.components.backup_plan.domain import BackupPlan, Path, BackupPlanId, Source, Destination
|
||||
from tui_rsync.core.shared_kernel.components.common import UUID, Label
|
||||
|
||||
|
||||
class FakeFactories:
|
||||
def __init__(self):
|
||||
self.faker = Faker()
|
||||
|
||||
def uuid(self):
|
||||
return UUID(self.faker.uuid4())
|
||||
|
||||
def backup_plan_id(self):
|
||||
return BackupPlanId(self.uuid().id)
|
||||
|
||||
def label(self) -> Label:
|
||||
return Label(self.faker.sentence(nb_words=4))
|
||||
|
||||
def path(self) -> Path:
|
||||
return Path(self.faker.file_path())
|
||||
|
||||
def source(self) -> Source:
|
||||
return Source(self.path().path)
|
||||
|
||||
def destination(self) -> Destination:
|
||||
return Destination(self.path().path)
|
||||
|
||||
def backup_plan(self, uuid: str | None = None):
|
||||
uuid = self.backup_plan_id() if uuid is None else UUID(uuid)
|
||||
return BackupPlan(id=uuid,
|
||||
label=self.label(),
|
||||
source=self.source(),
|
||||
destinations=[self.destination()])
|
@ -1,5 +0,0 @@
|
||||
import json
|
||||
|
||||
|
||||
def json_to_dict(json_data):
|
||||
return json.loads(json_data)
|
@ -1,32 +0,0 @@
|
||||
click==8.1.3 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
|
||||
--hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48
|
||||
colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and platform_system == "Windows" \
|
||||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
|
||||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
||||
injector==0.22.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:74379ccef3b893bc7d0b7d504c255b5160c5a55e97dc7bdcc73cb33cc7dce3a1 \
|
||||
--hash=sha256:79b2d4a0874c75d3aa735f11c5b32b89d9542711ca07071161882c5e9cc15ed6
|
||||
markdown-it-py==2.2.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30 \
|
||||
--hash=sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1
|
||||
mdurl==0.1.2 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
|
||||
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
|
||||
peewee==3.15.4 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:2581520c8dfbacd9d580c2719ae259f0637a9e46eda47dfc0ce01864c6366205
|
||||
platformdirs==3.1.1 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:024996549ee88ec1a9aa99ff7f8fc819bb59e2c3477b410d90a16d32d6e707aa \
|
||||
--hash=sha256:e5986afb596e4bb5bde29a79ac9061aa955b94fca2399b7aaac4090860920dd8
|
||||
pyfzf==0.3.1 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:736f71563461b75f6f85b55345bdc638fa0dc14c32c857c59e8b1ca1cfa3cf4a \
|
||||
--hash=sha256:dd902e34cffeca9c3082f96131593dd20b4b3a9bba5b9dde1b0688e424b46bd2
|
||||
pygments==2.14.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297 \
|
||||
--hash=sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717
|
||||
rich==13.3.1 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:125d96d20c92b946b983d0d392b84ff945461e5a06d3867e9f9e575f8697b67f \
|
||||
--hash=sha256:8aa57747f3fc3e977684f0176a88e789be314a99f99b43b75d1e9cb5dc6db9e9
|
||||
typer==0.7.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d \
|
||||
--hash=sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165
|
@ -1,53 +0,0 @@
|
||||
behave==1.2.6 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86 \
|
||||
--hash=sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c
|
||||
click==8.1.3 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
|
||||
--hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48
|
||||
colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and platform_system == "Windows" \
|
||||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
|
||||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
||||
faker==35.0.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:42f2da8cf561e38c72b25e9891168b1e25fec42b6b0b5b0b6cd6041da54af885 \
|
||||
--hash=sha256:926d2301787220e0554c2e39afc4dc535ce4b0a8d0a089657137999f66334ef4
|
||||
injector==0.22.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:74379ccef3b893bc7d0b7d504c255b5160c5a55e97dc7bdcc73cb33cc7dce3a1 \
|
||||
--hash=sha256:79b2d4a0874c75d3aa735f11c5b32b89d9542711ca07071161882c5e9cc15ed6
|
||||
markdown-it-py==2.2.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30 \
|
||||
--hash=sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1
|
||||
mdurl==0.1.2 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
|
||||
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
|
||||
parse-type==0.6.2 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:06d39a8b70fde873eb2a131141a0e79bb34a432941fb3d66fad247abafc9766c \
|
||||
--hash=sha256:79b1f2497060d0928bc46016793f1fca1057c4aacdf15ef876aa48d75a73a355
|
||||
parse==1.19.1 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:371ed3800dc63983832159cc9373156613947707bc448b5215473a219dbd4362 \
|
||||
--hash=sha256:cc3a47236ff05da377617ddefa867b7ba983819c664e1afe46249e5b469be464
|
||||
peewee==3.15.4 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:2581520c8dfbacd9d580c2719ae259f0637a9e46eda47dfc0ce01864c6366205
|
||||
platformdirs==3.1.1 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:024996549ee88ec1a9aa99ff7f8fc819bb59e2c3477b410d90a16d32d6e707aa \
|
||||
--hash=sha256:e5986afb596e4bb5bde29a79ac9061aa955b94fca2399b7aaac4090860920dd8
|
||||
pyfzf==0.3.1 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:736f71563461b75f6f85b55345bdc638fa0dc14c32c857c59e8b1ca1cfa3cf4a \
|
||||
--hash=sha256:dd902e34cffeca9c3082f96131593dd20b4b3a9bba5b9dde1b0688e424b46bd2
|
||||
pygments==2.14.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297 \
|
||||
--hash=sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717
|
||||
python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
|
||||
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
|
||||
rich==13.3.1 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:125d96d20c92b946b983d0d392b84ff945461e5a06d3867e9f9e575f8697b67f \
|
||||
--hash=sha256:8aa57747f3fc3e977684f0176a88e789be314a99f99b43b75d1e9cb5dc6db9e9
|
||||
six==1.16.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
|
||||
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
|
||||
typer==0.7.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d \
|
||||
--hash=sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165
|
||||
typing-extensions==4.12.2 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
|
||||
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
|
@ -0,0 +1,4 @@
|
||||
from tui_rsync.cli.cli import cli_app
|
||||
from tui_rsync.cli.sync import sync
|
||||
from tui_rsync.cli.rsync import Rsync
|
||||
from tui_rsync.cli.label_prompt import LabelPrompt
|
@ -0,0 +1 @@
|
||||
from tui_rsync.cli.groups.groups import groups
|
@ -0,0 +1,56 @@
|
||||
################################################################################
|
||||
# Copyright (C) 2023 Kostiantyn Klochko <kostya_klochko@ukr.net> #
|
||||
# #
|
||||
# This file is part of tui-rsync. #
|
||||
# #
|
||||
# tui-rsync is free software: you can redistribute it and/or modify it under #
|
||||
# uthe terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# tui-rsync is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along with #
|
||||
# tui-rsync. If not, see <https://www.gnu.org/licenses/>. #
|
||||
################################################################################
|
||||
|
||||
from rich.console import Console
|
||||
from rich.prompt import Prompt
|
||||
from typing import List, Optional
|
||||
import typer
|
||||
|
||||
from tui_rsync.models.models import Group
|
||||
from tui_rsync.models.models import all_group_labels
|
||||
from tui_rsync.cli.groups.group_prompt import GroupPrompt
|
||||
|
||||
console = Console()
|
||||
group_remove = typer.Typer()
|
||||
|
||||
@group_remove.command()
|
||||
def one(
|
||||
group_label: str = typer.Option(
|
||||
None, "--group-label", "-g",
|
||||
help="[b]The label[/] is a uniq identification of a [b]group[/].",
|
||||
show_default=False
|
||||
),
|
||||
):
|
||||
"""
|
||||
[red b]Remove[/] an [yellow]existing group[/].
|
||||
"""
|
||||
if group_label is None:
|
||||
group_label = GroupPrompt.get_label_fzf()
|
||||
|
||||
if Group.is_exist(group_label):
|
||||
group = Group.get_group(group_label)
|
||||
group.delete_instance()
|
||||
|
||||
@group_remove.command()
|
||||
def all():
|
||||
"""
|
||||
[red b]Remove[/] [yellow] all existing groups[/].
|
||||
"""
|
||||
for label in all_group_labels().iterator():
|
||||
one(label.label)
|
@ -0,0 +1,63 @@
|
||||
################################################################################
|
||||
# Copyright (C) 2023 Kostiantyn Klochko <kostya_klochko@ukr.net> #
|
||||
# #
|
||||
# This file is part of tui-rsync. #
|
||||
# #
|
||||
# tui-rsync is free software: you can redistribute it and/or modify it under #
|
||||
# uthe terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# tui-rsync is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along with #
|
||||
# tui-rsync. If not, see <https://www.gnu.org/licenses/>. #
|
||||
################################################################################
|
||||
|
||||
from rich.console import Console
|
||||
from rich.prompt import Prompt
|
||||
from typing import List, Optional
|
||||
import typer
|
||||
|
||||
from tui_rsync.models.models import Group, GroupSource
|
||||
from tui_rsync.cli.groups.group_prompt import GroupPrompt
|
||||
from tui_rsync.models.models import all_group_labels
|
||||
|
||||
console = Console()
|
||||
group_show = typer.Typer()
|
||||
|
||||
@group_show.command()
|
||||
def one(
|
||||
group_label: str = typer.Option(
|
||||
None, "--group-label", "-l",
|
||||
help="[b]The label[/] is a uniq identification of a [b]group[/].",
|
||||
show_default=False
|
||||
),
|
||||
):
|
||||
"""
|
||||
[green b]Show[/] an [yellow]existing group[/].
|
||||
"""
|
||||
if group_label is None:
|
||||
console.print("What is the [yellow b]label of the group[/]? ")
|
||||
group_label = GroupPrompt.get_label_fzf()
|
||||
|
||||
if not Group.is_exist(group_label):
|
||||
console.print("[red b][ERROR][/] Source does not exists!!!")
|
||||
return
|
||||
|
||||
group = Group.get_group(group_label)
|
||||
|
||||
console.print(group.show_format())
|
||||
|
||||
@group_show.command()
|
||||
def all():
|
||||
"""
|
||||
[green b]Show[/] [yellow]all existing groups[/].
|
||||
"""
|
||||
for label in all_group_labels().iterator():
|
||||
group = Group.get_group(label.label)
|
||||
console.print(group.show_format())
|
||||
|
@ -0,0 +1,89 @@
|
||||
################################################################################
|
||||
# Copyright (C) 2023 Kostiantyn Klochko <kostya_klochko@ukr.net> #
|
||||
# #
|
||||
# This file is part of tui-rsync. #
|
||||
# #
|
||||
# tui-rsync is free software: you can redistribute it and/or modify it under #
|
||||
# uthe terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# tui-rsync is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along with #
|
||||
# tui-rsync. If not, see <https://www.gnu.org/licenses/>. #
|
||||
################################################################################
|
||||
|
||||
from rich.console import Console
|
||||
from rich.prompt import Prompt
|
||||
from typing import List, Optional
|
||||
import typer
|
||||
|
||||
from tui_rsync.models.models import Group, GroupSource
|
||||
from tui_rsync.cli.label_prompt import LabelPrompt
|
||||
from tui_rsync.cli.groups.group_prompt import GroupPrompt
|
||||
from typer.main import get_group
|
||||
|
||||
console = Console()
|
||||
group_update = typer.Typer()
|
||||
|
||||
@group_update.command()
|
||||
def label(
|
||||
group_label: str = typer.Option(
|
||||
None, "--group-label", "-l",
|
||||
help="[b]The label[/] is a uniq identification of a [b]group[/].",
|
||||
show_default=False
|
||||
),
|
||||
new_group_label: str = typer.Option(
|
||||
None, "--new-group-label", "-nl",
|
||||
help="[b]The new label[/] will replace the [b]old group label[/].",
|
||||
show_default=False
|
||||
),
|
||||
|
||||
):
|
||||
"""
|
||||
[green b]Update[/] an [yellow]existing group label[/].
|
||||
"""
|
||||
if group_label is None:
|
||||
console.print("What is the [yellow b]old label of group[/]? ")
|
||||
group_label = GroupPrompt.get_label_fzf()
|
||||
|
||||
if new_group_label is None:
|
||||
question = "What is the [yellow b]new label of the group[/]? "
|
||||
new_group_label = GroupPrompt.ask_uuid(question)
|
||||
|
||||
if Group.is_exist(group_label):
|
||||
group = Group.get_group(group_label)
|
||||
group.update_label(new_group_label)
|
||||
|
||||
@group_update.command()
|
||||
def labels(
|
||||
group_label: str = typer.Option(
|
||||
None, "--group-label", "-g",
|
||||
help="[b]The label[/] is a uniq identification of a [b]group[/].",
|
||||
show_default=False
|
||||
),
|
||||
new_labels: str = typer.Option(
|
||||
None, "--new-labels", "-nl",
|
||||
help="[b]The new label[/] will replace the [b]old source label[/].",
|
||||
show_default=False
|
||||
),
|
||||
|
||||
):
|
||||
"""
|
||||
[green b]Update[/] [yellow]the group[/] with a [bold]the label[/].
|
||||
[b]The chosen sources[/] will be updated for [b]the group[/].
|
||||
"""
|
||||
if group_label is None:
|
||||
group_label = GroupPrompt.get_label_fzf()
|
||||
|
||||
if new_labels is None:
|
||||
new_labels = LabelPrompt.get_labels()
|
||||
|
||||
group = Group.get_group(group_label)
|
||||
group.remove_sources()
|
||||
GroupSource.create_group_sources(group, new_labels)
|
||||
group.save()
|
@ -0,0 +1,56 @@
|
||||
################################################################################
|
||||
# Copyright (C) 2023 Kostiantyn Klochko <kostya_klochko@ukr.net> #
|
||||
# #
|
||||
# This file is part of tui-rsync. #
|
||||
# #
|
||||
# tui-rsync is free software: you can redistribute it and/or modify it under #
|
||||
# uthe terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# tui-rsync is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along with #
|
||||
# tui-rsync. If not, see <https://www.gnu.org/licenses/>. #
|
||||
################################################################################
|
||||
|
||||
from rich.console import Console
|
||||
from rich.prompt import Confirm, Prompt
|
||||
from typing import List, Optional
|
||||
import typer
|
||||
|
||||
from tui_rsync.cli.label_prompt import LabelPrompt
|
||||
from tui_rsync.cli.rsync import Rsync
|
||||
from tui_rsync.models.models import Group, count_all_labels_except
|
||||
from tui_rsync.cli.groups.group_show import group_show
|
||||
from tui_rsync.cli.groups.group_update import group_update
|
||||
from tui_rsync.cli.groups.group_remove import group_remove
|
||||
|
||||
console = Console()
|
||||
groups = typer.Typer()
|
||||
groups.add_typer(group_show, name="show", help="Show groups")
|
||||
groups.add_typer(group_update, name="update", help="Update groups")
|
||||
groups.add_typer(group_remove, name="remove", help="Remove groups")
|
||||
|
||||
@groups.command()
|
||||
def add(
|
||||
group_label: str = typer.Option(
|
||||
None, "--group-label", "-g",
|
||||
help="[b]The label[/] is a uniq identification of a [b]group[/].",
|
||||
show_default=False
|
||||
),
|
||||
):
|
||||
"""
|
||||
[green b]Create[/] a [yellow]new group[/] with a [bold]uniq[/] label.
|
||||
[b]The chosen sources[/] will be united into [b]the group[/].
|
||||
"""
|
||||
if group_label is None:
|
||||
question = "Would you like to change [yellow b]the group label[/]?"
|
||||
group_label = LabelPrompt.ask_uuid(question)
|
||||
|
||||
labels = LabelPrompt.get_labels()
|
||||
Group.create_save(group_label, labels)
|
||||
|
@ -0,0 +1,70 @@
|
||||
################################################################################
|
||||
# Copyright (C) 2023 Kostiantyn Klochko <kostya_klochko@ukr.net> #
|
||||
# #
|
||||
# This file is part of tui-rsync. #
|
||||
# #
|
||||
# tui-rsync is free software: you can redistribute it and/or modify it under #
|
||||
# uthe terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# tui-rsync is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along with #
|
||||
# tui-rsync. If not, see <https://www.gnu.org/licenses/>. #
|
||||
################################################################################
|
||||
|
||||
from rich.console import Console
|
||||
from rich.prompt import Confirm, Prompt, IntPrompt
|
||||
from pyfzf import FzfPrompt
|
||||
import uuid
|
||||
from tui_rsync.models.models import all_labels, all_labels_except
|
||||
from tui_rsync.models.models import count_all_labels_except
|
||||
|
||||
console = Console()
|
||||
|
||||
class LabelPrompt:
|
||||
@staticmethod
|
||||
def ask_uuid(
|
||||
question: str = "Would you like to change [yellow b]the label[/]?"
|
||||
) -> str:
|
||||
"""
|
||||
Return the label or the default uuid value.
|
||||
"""
|
||||
uid = uuid.uuid4().hex
|
||||
label = Prompt.ask(question, default=uid)
|
||||
return label
|
||||
|
||||
@staticmethod
|
||||
def get_label_fzf() -> str:
|
||||
fzf = FzfPrompt()
|
||||
return fzf.prompt(all_labels().iterator())[0]
|
||||
|
||||
@staticmethod
|
||||
def get_label_except_fzf(labels = None) -> str:
|
||||
fzf = FzfPrompt()
|
||||
return fzf.prompt(all_labels_except(labels).iterator())[0]
|
||||
|
||||
@staticmethod
|
||||
def get_labels(labels = None) -> list:
|
||||
confirm_question = "Would you like to add a source/sources to the group?"
|
||||
is_fzf = Confirm.ask(confirm_question, default=True)
|
||||
|
||||
if not is_fzf:
|
||||
return []
|
||||
|
||||
count_question = "How much would you like to add sources to the group?"
|
||||
count = IntPrompt.ask(count_question, default=1)
|
||||
|
||||
count_max = count_all_labels_except(labels)
|
||||
count = count_max if count > count_max else count
|
||||
|
||||
labels = []
|
||||
for i in range(count):
|
||||
option = LabelPrompt.get_label_except_fzf(labels)
|
||||
labels.append(option)
|
||||
|
||||
return labels
|
@ -0,0 +1,40 @@
|
||||
################################################################################
|
||||
# Copyright (C) 2023 Kostiantyn Klochko <kostya_klochko@ukr.net> #
|
||||
# #
|
||||
# This file is part of tui-rsync. #
|
||||
# #
|
||||
# tui-rsync is free software: you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# tui-rsync is distributed in the hope that it will be useful, but #
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
|
||||
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along with #
|
||||
# tui-rsync. If not, see <https://www.gnu.org/licenses/>. #
|
||||
################################################################################
|
||||
|
||||
from sys import stderr
|
||||
from rich.console import Console
|
||||
from rich.prompt import Prompt
|
||||
from pyfzf import FzfPrompt
|
||||
import os
|
||||
from tui_rsync.models.models import Destination, Path
|
||||
|
||||
console = Console()
|
||||
err_console = Console(stderr=True)
|
||||
|
||||
class PathPrompt:
|
||||
@staticmethod
|
||||
def get_backup_fzf(label:str) -> str:
|
||||
dests_count = len(Destination.get_all(label))
|
||||
if dests_count == 0:
|
||||
err_console.print("[red b]No backups!!![/]")
|
||||
return ""
|
||||
if dests_count == 1:
|
||||
return Destination.get_all(label).get()
|
||||
fzf = FzfPrompt()
|
||||
return fzf.prompt(Destination.get_all(label).iterator())[0]
|
@ -0,0 +1 @@
|
||||
from tui_rsync.cli.source.source import source
|
@ -0,0 +1,56 @@
|
||||
################################################################################
|
||||
# Copyright (C) 2023 Kostiantyn Klochko <kostya_klochko@ukr.net> #
|
||||
# #
|
||||
# This file is part of tui-rsync. #
|
||||
# #
|
||||
# tui-rsync is free software: you can redistribute it and/or modify it under #
|
||||
# uthe terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# tui-rsync is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along with #
|
||||
# tui-rsync. If not, see <https://www.gnu.org/licenses/>. #
|
||||
################################################################################
|
||||
|
||||
from rich.console import Console
|
||||
from rich.prompt import Prompt
|
||||
from typing import List, Optional
|
||||
import typer
|
||||
|
||||
from tui_rsync.models.models import Source, Destination, SyncCommand, Path
|
||||
from tui_rsync.cli.label_prompt import LabelPrompt
|
||||
from tui_rsync.models.models import all_labels
|
||||
|
||||
console = Console()
|
||||
source_remove = typer.Typer()
|
||||
|
||||
@source_remove.command()
|
||||
def one(
|
||||
label: str = typer.Option(
|
||||
None, "--label", "-l",
|
||||
help="[b]The label[/] is a uniq identification of a [b]source[/].",
|
||||
show_default=False
|
||||
)
|
||||
):
|
||||
"""
|
||||
[red b]Remove[/] an [yellow]existing source[/].
|
||||
"""
|
||||
if label is None:
|
||||
label = LabelPrompt.get_label_fzf()
|
||||
|
||||
if Source.is_exist(label):
|
||||
src = Source.get_source(label)
|
||||
src.delete_instance()
|
||||
|
||||
@source_remove.command()
|
||||
def all():
|
||||
"""
|
||||
[red b]Remove[/] [yellow] all existing sources[/].
|
||||
"""
|
||||
for label in all_labels().iterator():
|
||||
one(label.label)
|
@ -0,0 +1,64 @@
|
||||
################################################################################
|
||||
# Copyright (C) 2023 Kostiantyn Klochko <kostya_klochko@ukr.net> #
|
||||
# #
|
||||
# This file is part of tui-rsync. #
|
||||
# #
|
||||
# tui-rsync is free software: you can redistribute it and/or modify it under #
|
||||
# uthe terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# tui-rsync is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along with #
|
||||
# tui-rsync. If not, see <https://www.gnu.org/licenses/>. #
|
||||
################################################################################
|
||||
|
||||
from rich.console import Console
|
||||
from rich.prompt import Prompt
|
||||
from typing import List, Optional
|
||||
import typer
|
||||
|
||||
from tui_rsync.models.models import Source, Destination, SyncCommand, Path
|
||||
from tui_rsync.cli.label_prompt import LabelPrompt
|
||||
from tui_rsync.models.models import all_labels
|
||||
|
||||
console = Console()
|
||||
source_show = typer.Typer()
|
||||
|
||||
@source_show.command()
|
||||
def one(
|
||||
label: str = typer.Option(
|
||||
None, "--label", "-l",
|
||||
help="[b]The label[/] is a uniq identification of a [b]source[/].",
|
||||
show_default=False
|
||||
),
|
||||
):
|
||||
"""
|
||||
[green b]Show[/] an [yellow]existing source[/].
|
||||
"""
|
||||
if label is None:
|
||||
console.print("What is the [yellow b]label of source[/]? ")
|
||||
label = LabelPrompt.get_label_fzf()
|
||||
|
||||
if not Source.is_exist(label):
|
||||
console.print("[red b][ERROR][/] Source does not exists!!!")
|
||||
return
|
||||
|
||||
source = Source.get_source(label)
|
||||
|
||||
console.print(source.show_format())
|
||||
|
||||
@source_show.command()
|
||||
def all():
|
||||
"""
|
||||
[green b]Show[/] [yellow]all existing sources[/].
|
||||
"""
|
||||
|
||||
for label in all_labels().iterator():
|
||||
source = Source.get_source(label.label)
|
||||
console.print(source.show_format())
|
||||
|
@ -0,0 +1,111 @@
|
||||
################################################################################
|
||||
# Copyright (C) 2023 Kostiantyn Klochko <kostya_klochko@ukr.net> #
|
||||
# #
|
||||
# This file is part of tui-rsync. #
|
||||
# #
|
||||
# tui-rsync is free software: you can redistribute it and/or modify it under #
|
||||
# uthe terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# tui-rsync is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along with #
|
||||
# tui-rsync. If not, see <https://www.gnu.org/licenses/>. #
|
||||
################################################################################
|
||||
|
||||
from rich.console import Console
|
||||
from rich.prompt import Prompt
|
||||
from typing import List, Optional
|
||||
import typer
|
||||
|
||||
from tui_rsync.models.models import Source, Destination, SyncCommand, Path
|
||||
from tui_rsync.cli.label_prompt import LabelPrompt
|
||||
|
||||
console = Console()
|
||||
source_update = typer.Typer()
|
||||
|
||||
@source_update.command()
|
||||
def label(
|
||||
label: str = typer.Option(
|
||||
None, "--label", "-l",
|
||||
help="[b]The label[/] is a uniq identification of a [b]source[/].",
|
||||
show_default=False
|
||||
),
|
||||
new_label: str = typer.Option(
|
||||
None, "--new-label", "-nl",
|
||||
help="[b]The new label[/] will replace the [b]old source label[/].",
|
||||
show_default=False
|
||||
),
|
||||
|
||||
):
|
||||
"""
|
||||
[green b]Update[/] an [yellow]existing source label[/].
|
||||
"""
|
||||
if label is None:
|
||||
console.print("What is the [yellow b]old label of source[/]? ")
|
||||
label = LabelPrompt.get_label_fzf()
|
||||
|
||||
if new_label is None:
|
||||
question = "What is the [yellow b]new label of the source[/]? "
|
||||
new_label = LabelPrompt.ask_uuid(question)
|
||||
|
||||
if Source.is_exist(label):
|
||||
src = Source.get_source(label)
|
||||
src.update_label(new_label)
|
||||
|
||||
@source_update.command()
|
||||
def source(
|
||||
label: str = typer.Option(
|
||||
None, "--label", "-l",
|
||||
help="[b]The label[/] is a uniq identification of a [b]source[/].",
|
||||
show_default=False
|
||||
),
|
||||
new_source_path: str = typer.Option(
|
||||
None, "--new-label", "-nl",
|
||||
help="[b]The new source[/] will replace the [b]old source[/].",
|
||||
show_default=False
|
||||
),
|
||||
):
|
||||
"""
|
||||
[green b]Update[/] a source path of an [yellow]existing source[/].
|
||||
"""
|
||||
if label is None:
|
||||
console.print("What is the [yellow b]label of source[/]? ")
|
||||
label = LabelPrompt.get_label_fzf()
|
||||
|
||||
if new_source_path is None:
|
||||
question = "What is the [yellow b]new source path of the source[/]? "
|
||||
new_source_path = console.input(question)
|
||||
|
||||
if Source.is_exist(label):
|
||||
src = Source.get_source(label)
|
||||
src.update_source_path(new_source_path)
|
||||
|
||||
@source_update.command()
|
||||
def args(
|
||||
label: str = typer.Option(
|
||||
None, "--label", "-l",
|
||||
help="[b]The label[/] is a uniq identification of a [b]source[/].",
|
||||
show_default=False
|
||||
),
|
||||
args: str = typer.Option(
|
||||
None, "--args", "-a",
|
||||
help="[b yellow]rsync[/] [b]arguments[/].",
|
||||
show_default=False
|
||||
)
|
||||
|
||||
):
|
||||
"""
|
||||
[green b]Update[/] an [yellow]existing source args[/].
|
||||
"""
|
||||
if args is None:
|
||||
args = console.input("What is the [yellow b]rsync args of source[/]? ")
|
||||
|
||||
if Source.is_exist(label):
|
||||
src = Source.get_source(label)
|
||||
src.update_args(args)
|
||||
|
@ -0,0 +1,115 @@
|
||||
################################################################################
|
||||
# Copyright (C) 2023 Kostiantyn Klochko <kostya_klochko@ukr.net> #
|
||||
# #
|
||||
# This file is part of tui-rsync. #
|
||||
# #
|
||||
# tui-rsync is free software: you can redistribute it and/or modify it under #
|
||||
# uthe terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# tui-rsync is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along with #
|
||||
# tui-rsync. If not, see <https://www.gnu.org/licenses/>. #
|
||||
################################################################################
|
||||
|
||||
from rich.console import Console
|
||||
from rich.prompt import Prompt
|
||||
from typing import List, Optional
|
||||
import typer
|
||||
|
||||
from tui_rsync.models.models import Source, Group, Destination, SyncCommand
|
||||
from tui_rsync.models.models import Path
|
||||
from tui_rsync.models.models import all_labels
|
||||
from tui_rsync.cli.label_prompt import LabelPrompt
|
||||
from tui_rsync.cli.groups.group_prompt import GroupPrompt
|
||||
from tui_rsync.cli.path_prompt import PathPrompt
|
||||
from tui_rsync.cli.rsync import Rsync
|
||||
|
||||
console = Console()
|
||||
sync = typer.Typer()
|
||||
|
||||
skip_error = "[yellow b]Skippped[/] because the [red b]path was unavailable[/]."
|
||||
|
||||
@sync.command()
|
||||
def one(
|
||||
label: str = typer.Option(
|
||||
None, "--label", "-l",
|
||||
help="[b]The label[/] is a uniq identification of a [b]source[/].",
|
||||
show_default=False
|
||||
),
|
||||
dry: bool = typer.Option(
|
||||
False, "-d", "--dry-run",
|
||||
help="The command will [b]show[/] information about what will be changed.",
|
||||
)
|
||||
):
|
||||
"""
|
||||
[green b]Sync[/] a [yellow]source[/] with the [yellow b]label[/] and its backups.
|
||||
[yellow b]Skips[/] if not available.
|
||||
"""
|
||||
if label is None:
|
||||
label = LabelPrompt.get_label_fzf()
|
||||
src = Source.get_source(label)
|
||||
rsync = Rsync(str(src.args))
|
||||
for dest in src.destinations:
|
||||
if not dest.path.is_exists():
|
||||
console.print(skip_error)
|
||||
continue
|
||||
if dry:
|
||||
response = rsync.dry_one(str(src.source), str(dest))
|
||||
out, err = response
|
||||
console.print(f"{bstr_nonan(out)} {bstr_nonan(err)}")
|
||||
else:
|
||||
rsync.run_one(str(src.source), str(dest))
|
||||
|
||||
@sync.command()
|
||||
def group(
|
||||
group_label: str = typer.Option(
|
||||
None, "--group-label", "-g",
|
||||
help="[b]The label[/] is a uniq identification of a [b]group[/].",
|
||||
show_default=False
|
||||
),
|
||||
):
|
||||
"""
|
||||
[green b]Sync[/] a [yellow]group[/] with the [yellow b] label[/].
|
||||
"""
|
||||
if group_label is None:
|
||||
group_label = GroupPrompt.get_label_fzf()
|
||||
group = Group.get_group(group_label)
|
||||
|
||||
for src in group.get_sources():
|
||||
one(src.label)
|
||||
|
||||
@sync.command()
|
||||
def all():
|
||||
"""
|
||||
[green b]Sync[/] [yellow]all sources[/] with theirs backups.
|
||||
"""
|
||||
for label in all_labels().iterator():
|
||||
one(label.label)
|
||||
|
||||
@sync.command()
|
||||
def recover(
|
||||
label: str = typer.Option(
|
||||
None, "--label", "-l",
|
||||
help="[b]The label[/] is a uniq identification of a [b]source[/].",
|
||||
show_default=False
|
||||
),
|
||||
):
|
||||
"""
|
||||
[green b]Sync[/] the [yellow]chosen backup[/] with its source.
|
||||
"""
|
||||
if label is None:
|
||||
label = LabelPrompt.get_label_fzf()
|
||||
src = Source.get(Source.label == label)
|
||||
dest = PathPrompt.get_backup_fzf(label)
|
||||
|
||||
rsync = Rsync(str(src.args))
|
||||
rsync.run_one(str(dest), str(src.source))
|
||||
|
||||
def bstr_nonan(obj):
|
||||
return "" if obj is None else obj.decode()
|
@ -0,0 +1,2 @@
|
||||
from tui_rsync.config.app import App
|
||||
|
@ -1,5 +0,0 @@
|
||||
from .backup_command_port import BackupCommandPort
|
||||
from .backup_sync_command import BackupSyncCommand
|
||||
from .backup_sync_command_dry_run import BackupSyncCommandDryRun
|
||||
|
||||
__all__ = ['BackupCommandPort', 'BackupSyncCommand', 'BackupSyncCommandDryRun']
|
@ -1,7 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class BackupCommandPort(ABC):
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
pass
|
@ -1,14 +0,0 @@
|
||||
import shlex
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from . import BackupCommandPort
|
||||
|
||||
|
||||
class BackupSyncCommand(BackupCommandPort):
|
||||
def __init__(self, source: str, destination: str, args: str):
|
||||
self._args = ["rsync"] + shlex.split(args) + [source, destination]
|
||||
|
||||
def run(self):
|
||||
output = Popen(self._args, stdout=PIPE)
|
||||
response = output.communicate()
|
||||
|
@ -1,14 +0,0 @@
|
||||
import shlex
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from . import BackupCommandPort
|
||||
|
||||
|
||||
class BackupSyncCommandDryRun(BackupCommandPort):
|
||||
def __init__(self, source: str, destination: str, args: str):
|
||||
self._args = ["rsync"] + shlex.split(args) + ['--dry-run', source, destination]
|
||||
|
||||
def run(self):
|
||||
output = Popen(self._args, stdout=PIPE)
|
||||
return output.communicate()
|
||||
|
@ -1,5 +0,0 @@
|
||||
from .remove_all_backup_plans_command import RemoveAllBackupPlansCommand
|
||||
from .remove_backup_plan_destinations_command import RemoveBackupPlanDestinationsCommand
|
||||
from .remove_backup_plan_command import RemoveBackupPlanCommand
|
||||
|
||||
__all__ = ['RemoveAllBackupPlansCommand', 'RemoveBackupPlanCommand', 'RemoveBackupPlanDestinationsCommand']
|
@ -1,12 +0,0 @@
|
||||
from tui_rsync.core.ports.orm import DatabaseManagerPort
|
||||
from tui_rsync.infrastructure.orm.models import BackupPlanModel, DestinationModel
|
||||
|
||||
|
||||
class RemoveAllBackupPlansCommand:
|
||||
def __init__(self, database_manager: DatabaseManagerPort):
|
||||
self.databaseManager = database_manager
|
||||
|
||||
def execute(self) -> bool:
|
||||
rows = BackupPlanModel.delete().execute()
|
||||
rows = DestinationModel.delete().execute() + rows
|
||||
return rows != 0
|
@ -1,18 +0,0 @@
|
||||
from tui_rsync.core.ports.orm import DatabaseManagerPort
|
||||
from tui_rsync.core.shared_kernel.components.common import UUID
|
||||
from tui_rsync.core.shared_kernel.ports.Exceptions import CommandException
|
||||
from tui_rsync.infrastructure.orm.models import DestinationModel, BackupPlanModel
|
||||
|
||||
|
||||
class RemoveBackupPlanCommand:
|
||||
def __init__(self, database_manager: DatabaseManagerPort):
|
||||
self.databaseManager = database_manager
|
||||
|
||||
def execute(self, uuid: UUID) -> bool:
|
||||
deleted = BackupPlanModel.delete().where(BackupPlanModel.id == uuid.id).execute()
|
||||
deleted = DestinationModel.delete().where(DestinationModel.source == uuid.id).execute() | deleted
|
||||
|
||||
if deleted == 0:
|
||||
raise CommandException("Failed to delete the backup plan, because it doesn't exist.")
|
||||
|
||||
return True
|
@ -1,12 +0,0 @@
|
||||
from tui_rsync.core.ports.orm import DatabaseManagerPort
|
||||
from tui_rsync.core.shared_kernel.components.common import UUID
|
||||
from tui_rsync.infrastructure.orm.models import DestinationModel
|
||||
|
||||
|
||||
class RemoveBackupPlanDestinationsCommand:
|
||||
def __init__(self, database_manager: DatabaseManagerPort):
|
||||
self.databaseManager = database_manager
|
||||
|
||||
def execute(self, backup_plan_uuid: UUID) -> bool:
|
||||
rows = DestinationModel.delete().where(DestinationModel.source == backup_plan_uuid.id).execute()
|
||||
return rows != 0
|
@ -1,5 +0,0 @@
|
||||
from .get_all_backup_plans_query import GetAllBackupPlansQuery
|
||||
from .get_backup_plan_by_id_query import GetBackupPlanByIdQuery
|
||||
from .get_backup_plan_count_query import GetBackupPlanCountQuery
|
||||
|
||||
__all__ = ['GetAllBackupPlansQuery', 'GetBackupPlanByIdQuery', 'GetBackupPlanCountQuery']
|
@ -1,12 +0,0 @@
|
||||
from tui_rsync.core.ports.orm import DatabaseManagerPort
|
||||
from tui_rsync.infrastructure.orm.dto.dtos import BackupPlanDTO
|
||||
|
||||
from tui_rsync.infrastructure.orm.models import BackupPlanModel
|
||||
|
||||
|
||||
class GetAllBackupBackupPlansQuery:
|
||||
def __init__(self, database_manager: DatabaseManagerPort):
|
||||
self.databaseManager = database_manager
|
||||
|
||||
def execute(self):
|
||||
return (BackupPlanDTO.to_domain(model) for model in BackupPlanModel.select().iterator())
|
@ -1,12 +0,0 @@
|
||||
from tui_rsync.core.ports.orm import DatabaseManagerPort
|
||||
from tui_rsync.infrastructure.orm.dto.dtos import BackupPlanDTO
|
||||
|
||||
from tui_rsync.infrastructure.orm.models import BackupPlanModel
|
||||
|
||||
|
||||
class GetAllBackupPlansQuery:
|
||||
def __init__(self, database_manager: DatabaseManagerPort):
|
||||
self.databaseManager = database_manager
|
||||
|
||||
def execute(self):
|
||||
return (BackupPlanDTO.to_domain(model) for model in BackupPlanModel.select().iterator())
|
@ -1,20 +0,0 @@
|
||||
from tui_rsync.core.components.backup_plan.domain import BackupPlan
|
||||
from tui_rsync.core.ports.orm import DatabaseManagerPort
|
||||
from tui_rsync.core.shared_kernel.components.common import UUID
|
||||
from tui_rsync.core.shared_kernel.ports.Exceptions.query_exception import QueryException
|
||||
from tui_rsync.infrastructure.orm.dto.dtos import BackupPlanDTO
|
||||
|
||||
from tui_rsync.infrastructure.orm.models import BackupPlanModel
|
||||
|
||||
|
||||
class GetBackupPlanByIdQuery:
|
||||
def __init__(self, database_manager: DatabaseManagerPort):
|
||||
self.databaseManager = database_manager
|
||||
|
||||
def execute(self, uuid: UUID) -> BackupPlan:
|
||||
model = BackupPlanModel.get_or_none(BackupPlanModel.id == uuid.id)
|
||||
|
||||
if model is None:
|
||||
raise QueryException("The backup plan was not found.")
|
||||
|
||||
return BackupPlanDTO.to_domain(model)
|
@ -1,11 +0,0 @@
|
||||
from tui_rsync.core.ports.orm import DatabaseManagerPort
|
||||
|
||||
from tui_rsync.infrastructure.orm.models import BackupPlanModel
|
||||
|
||||
|
||||
class GetBackupPlanCountQuery:
|
||||
def __init__(self, database_manager: DatabaseManagerPort):
|
||||
self.databaseManager = database_manager
|
||||
|
||||
def execute(self):
|
||||
return BackupPlanModel.select().count()
|
@ -1,4 +0,0 @@
|
||||
from .backup_plan_repository_port import BackupPlanRepositoryPort
|
||||
from .backup_plan_repository import BackupPlanRepository
|
||||
|
||||
__all__ = ['BackupPlanRepositoryPort', 'BackupPlanRepository']
|
@ -1,49 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
from tui_rsync.core.ports.orm import DatabaseManagerPort
|
||||
from tui_rsync.infrastructure.orm.dto.dtos import BackupPlanDTO
|
||||
from .backup_plan_repository_port import BackupPlanRepositoryPort
|
||||
|
||||
from tui_rsync.core.components.backup_plan.domain import BackupPlan
|
||||
from tui_rsync.core.shared_kernel.components.common import UUID
|
||||
|
||||
from tui_rsync.infrastructure.orm.models import BackupPlanModel
|
||||
from ..commands import RemoveBackupPlanDestinationsCommand, RemoveBackupPlanCommand
|
||||
from ..queries import GetBackupPlanByIdQuery
|
||||
|
||||
|
||||
class BackupPlanRepository(BackupPlanRepositoryPort):
|
||||
def __init__(self, database_manager: DatabaseManagerPort):
|
||||
self.databaseManager = database_manager
|
||||
|
||||
def add(self, backup_plan: BackupPlan):
|
||||
model = BackupPlanDTO.to_model(backup_plan)
|
||||
|
||||
model.save(force_insert=True)
|
||||
for destination in model.destinations:
|
||||
destination.save(force_insert=True)
|
||||
|
||||
def get_by_id(self, uuid: UUID) -> BackupPlan:
|
||||
query = GetBackupPlanByIdQuery(self.databaseManager)
|
||||
return query.execute(uuid)
|
||||
|
||||
def update(self, backup_plan: BackupPlan):
|
||||
updated_model = BackupPlanDTO.to_model(backup_plan)
|
||||
query = GetBackupPlanByIdQuery(self.databaseManager)
|
||||
|
||||
old_plan = query.execute(backup_plan.id)
|
||||
model = BackupPlanDTO.to_model(old_plan)
|
||||
remove_backup_plan_destinations_command = RemoveBackupPlanDestinationsCommand(self.databaseManager)
|
||||
|
||||
model.label = updated_model.label
|
||||
model.source = updated_model.source
|
||||
|
||||
remove_backup_plan_destinations_command.execute(backup_plan.id)
|
||||
|
||||
model.save()
|
||||
for destination in updated_model.destinations:
|
||||
destination.save(force_insert=True)
|
||||
|
||||
def delete(self, uuid: UUID) -> bool:
|
||||
command = RemoveBackupPlanCommand(self.databaseManager)
|
||||
return command.execute(uuid)
|
@ -1,23 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
|
||||
from tui_rsync.core.components.backup_plan.domain import BackupPlan
|
||||
from tui_rsync.core.shared_kernel.components.common import UUID
|
||||
|
||||
|
||||
class BackupPlanRepositoryPort(ABC):
|
||||
@abstractmethod
|
||||
def add(self, backup_plan: BackupPlan):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_by_id(self, uuid: UUID) -> Optional[BackupPlan]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update(self, backup_plan: BackupPlan):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete(self, uuid: UUID) -> bool:
|
||||
pass
|
@ -1,11 +0,0 @@
|
||||
from .backup_plan_service import BackupPlanService
|
||||
from .get_all_backup_plans_service import GetAllBackupPlansService
|
||||
from .get_backup_plan_count_service import GetBackupPlanCountService
|
||||
from .remove_all_backup_plans_service import RemoveAllBackupPlansService
|
||||
|
||||
__all__ = [
|
||||
'BackupPlanService',
|
||||
'GetAllBackupPlansService',
|
||||
'GetBackupPlanCountService',
|
||||
'RemoveAllBackupPlansService',
|
||||
]
|
@ -1,23 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
from tui_rsync.core.components.backup_plan.application.repository import BackupPlanRepositoryPort
|
||||
|
||||
from tui_rsync.core.components.backup_plan.domain import BackupPlan
|
||||
from tui_rsync.core.shared_kernel.components.common import UUID
|
||||
|
||||
|
||||
class BackupPlanService:
|
||||
def __init__(self, backup_plan_repository: BackupPlanRepositoryPort):
|
||||
self.backup_plan_repository = backup_plan_repository
|
||||
|
||||
def add(self, backup_plan: BackupPlan):
|
||||
self.backup_plan_repository.add(backup_plan)
|
||||
|
||||
def get_by_id(self, uuid: UUID) -> Optional[BackupPlan]:
|
||||
return self.backup_plan_repository.get_by_id(uuid)
|
||||
|
||||
def update(self, backup_plan: BackupPlan):
|
||||
return self.backup_plan_repository.update(backup_plan)
|
||||
|
||||
def delete(self, uuid: UUID) -> bool:
|
||||
return self.backup_plan_repository.delete(uuid)
|
@ -1,13 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from tui_rsync.core.components.backup_plan.application.queries import GetAllBackupPlansQuery
|
||||
|
||||
from tui_rsync.core.components.backup_plan.domain import BackupPlan
|
||||
|
||||
|
||||
class GetAllBackupPlansService:
|
||||
def __init__(self, get_all_backup_plan_query: GetAllBackupPlansQuery):
|
||||
self.get_all_backup_plan_query = get_all_backup_plan_query
|
||||
|
||||
def get_all(self) -> List[BackupPlan]:
|
||||
return self.get_all_backup_plan_query.execute()
|
@ -1,16 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from tui_rsync.core.components.backup_plan.application.queries import GetBackupPlanCountQuery
|
||||
|
||||
from tui_rsync.core.components.backup_plan.domain import BackupPlan
|
||||
|
||||
|
||||
class GetBackupPlanCountService:
|
||||
def __init__(self, get_backup_plan_count_query: GetBackupPlanCountQuery):
|
||||
self.get_backup_plan_count_query = get_backup_plan_count_query
|
||||
|
||||
def count(self) -> int:
|
||||
return self.get_backup_plan_count_query.execute()
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
return self.count() == 0
|
@ -1,9 +0,0 @@
|
||||
from tui_rsync.core.components.backup_plan.application.commands import RemoveAllBackupPlansCommand
|
||||
|
||||
|
||||
class RemoveAllBackupPlansService:
|
||||
def __init__(self, remove_all_backup_plan_command: RemoveAllBackupPlansCommand):
|
||||
self.remove_all_backup_plan_command = remove_all_backup_plan_command
|
||||
|
||||
def remove_all(self) -> bool:
|
||||
return self.remove_all_backup_plan_command.execute()
|
@ -1,4 +0,0 @@
|
||||
from .entities import BackupPlan
|
||||
from .value_objects import BackupPlanId, Path, Source, Destination
|
||||
|
||||
__all__ = ['BackupPlan', 'BackupPlanId', 'Path', 'Source', 'Destination']
|
@ -1,3 +0,0 @@
|
||||
from .backup_plan import BackupPlan
|
||||
|
||||
__all__ = ['BackupPlan']
|
@ -1,14 +0,0 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from ..value_objects import BackupPlanId, Source, Destination
|
||||
from tui_rsync.core.shared_kernel.components.common.domain import Label
|
||||
|
||||
|
||||
@dataclass
|
||||
class BackupPlan:
|
||||
source: Source
|
||||
destinations: List[Destination]
|
||||
|
||||
id: BackupPlanId = field(default_factory=lambda: BackupPlanId.generate())
|
||||
label: Label = field(default='')
|
@ -1,6 +0,0 @@
|
||||
from .backup_plan_id import BackupPlanId
|
||||
from .path import Path
|
||||
from .source import Source
|
||||
from .destionation import Destination
|
||||
|
||||
__all__ = ['BackupPlanId', 'Path', 'Source', 'Destination']
|
@ -1,5 +0,0 @@
|
||||
from tui_rsync.core.shared_kernel.components.common.domain import UUID
|
||||
|
||||
|
||||
class BackupPlanId(UUID):
|
||||
pass
|
@ -1,5 +0,0 @@
|
||||
from .path import Path
|
||||
|
||||
|
||||
class Destination(Path):
|
||||
pass
|
@ -1,6 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Path:
|
||||
path: str
|
@ -1,5 +0,0 @@
|
||||
from .path import Path
|
||||
|
||||
|
||||
class Source(Path):
|
||||
pass
|
@ -1,3 +0,0 @@
|
||||
from .user_data_paths_port import UserDataPathsPort
|
||||
|
||||
__all__ = ['UserDataPathsPort']
|
@ -1,15 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class UserDataPathsPort(ABC):
|
||||
@abstractmethod
|
||||
def safe_create_user_data_dir(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_user_db_path(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_user_data_dir(self):
|
||||
pass
|
@ -1,3 +0,0 @@
|
||||
from .database_manager_port import DatabaseManagerPort
|
||||
|
||||
__all__ = ['DatabaseManagerPort']
|
@ -1,11 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class DatabaseManagerPort(ABC):
|
||||
@abstractmethod
|
||||
def get_connection(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_tables(self):
|
||||
pass
|
@ -1,3 +0,0 @@
|
||||
from .backup_sync_service import BackupSyncService
|
||||
|
||||
__all__ = ['BackupSyncService']
|
@ -1,22 +0,0 @@
|
||||
from tui_rsync.core.components.backup.application.backup_commands import BackupSyncCommand, BackupSyncCommandDryRun
|
||||
from tui_rsync.core.components.backup_plan.application.repository import BackupPlanRepositoryPort
|
||||
from tui_rsync.core.shared_kernel.components.common import UUID
|
||||
from tui_rsync.user_interface.cli.shared_kernel.components.prompts.applications.prompts import ChoosePromptPort
|
||||
|
||||
|
||||
class BackupRestoreService:
|
||||
def __init__(self, backup_plan_repository: BackupPlanRepositoryPort):
|
||||
self.backup_plan_repository = backup_plan_repository
|
||||
|
||||
def sync_by_plan_id(self, uuid: UUID, choose_prompt: ChoosePromptPort, args: str = '', dry_run: bool = False):
|
||||
backup_plan = self.backup_plan_repository.get_by_id(uuid)
|
||||
|
||||
destinations = (destination.path for destination in backup_plan.destinations)
|
||||
destination = choose_prompt.choose(destinations)
|
||||
|
||||
if dry_run:
|
||||
backup_command = BackupSyncCommandDryRun(destination, backup_plan.source.path, args)
|
||||
backup_command.run()
|
||||
else:
|
||||
backup_command = BackupSyncCommand(destination, backup_plan.source.path, args)
|
||||
backup_command.run()
|
@ -1,20 +0,0 @@
|
||||
from tui_rsync.core.components.backup.application.backup_commands import BackupSyncCommand, BackupSyncCommandDryRun
|
||||
from tui_rsync.core.components.backup_plan.application.repository import BackupPlanRepositoryPort
|
||||
from tui_rsync.core.shared_kernel.components.common import UUID
|
||||
|
||||
|
||||
class BackupSyncService:
|
||||
def __init__(self, backup_plan_repository: BackupPlanRepositoryPort):
|
||||
self.backup_plan_repository = backup_plan_repository
|
||||
|
||||
def sync_by_plan_id(self, uuid: UUID, args: str = '', dry_run: bool = False):
|
||||
backup_plan = self.backup_plan_repository.get_by_id(uuid)
|
||||
|
||||
if dry_run:
|
||||
for destination in backup_plan.destinations:
|
||||
backup_command = BackupSyncCommandDryRun(backup_plan.source.path, destination.path, args)
|
||||
backup_command.run()
|
||||
else:
|
||||
for destination in backup_plan.destinations:
|
||||
backup_command = BackupSyncCommand(backup_plan.source.path, destination.path, args)
|
||||
backup_command.run()
|
@ -1,3 +0,0 @@
|
||||
from .domain import UUID, Label
|
||||
|
||||
__all__ = ['UUID', 'Label']
|
@ -1,3 +0,0 @@
|
||||
from .value_objects import UUID, Label
|
||||
|
||||
__all__ = ['UUID', 'Label']
|
@ -1,4 +0,0 @@
|
||||
from .uuid import UUID
|
||||
from .label import Label
|
||||
|
||||
__all__ = ['UUID', 'Label']
|
@ -1,6 +0,0 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Label:
|
||||
label: str = field(default='')
|
@ -1,14 +0,0 @@
|
||||
from dataclasses import dataclass, field
|
||||
import uuid
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UUID:
|
||||
id: str = field(default_factory=lambda: UUID.generate().id)
|
||||
|
||||
@classmethod
|
||||
def generate(cls) -> 'UUID':
|
||||
return cls(str(uuid.uuid4()))
|
||||
|
||||
def __str__(self):
|
||||
return self.id
|
@ -1,4 +0,0 @@
|
||||
from .app_exception import AppException
|
||||
from .command_exception import CommandException
|
||||
|
||||
__all__ = ['AppException', 'CommandException']
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue