Skip to content

Commit

Permalink
Merge pull request #11 from GedasFX/feature/9
Browse files Browse the repository at this point in the history
Advanced filtering capabilites
  • Loading branch information
GedasFX authored Apr 2, 2023
2 parents 895c9d0 + 0082154 commit df33b81
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 112 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Support: **[SteamDeckHomebrew Discord](https://discord.gg/ZU74G2NJzk)**.
* A high variety of supported cloud providers, courtesy of [rclone](https://rclone.org/).
* Ability to back up files automatically after a game is closed.
* File counter, which estimates the number of files a sync path would pick up. Prevents accidental backing up of the entire steam deck.
* Advanced filtering, allowing users to set up custom filtering rules with includes and excludes.

**IMPORTANT!** This plugin does not support bidirectional sync. In other words, this plugin is not intended for use cases to sync saves between devices, but rather just to keep your game progress safe in case of data loss.

Expand Down Expand Up @@ -72,3 +73,13 @@ Manual configuration would require the use of a console, but following the inter
3. Verify the sync works by going to gaming mode and clicking `Sync now`. If files start appearing in the cloud, everything works as expected.

### Filtering (Advanced)

By default, the filters defined in the UI can be set to include some paths and to exclude others (excludes takes priority over includes). If a more complex solution is needed, you can edit the filter file (by default: `/home/deck/homebrew/settings/decky-cloud-save/sync_paths_filter.txt`) to meet your needs.

Important note: UI edits overwrite this file, so make sure you do not use the built-in editor afterward and make constant backups of the configuration (heh). Additionally, we cannot check for the validity of the configuration after it was done manually. If needed, a dry-run can be performed to check which files would have been synced after the edit. I would highly recommend you do it before you sync your entire file system.

Command (in `/home/deck/homebrew/plugins/decky-cloud-save/`):
```bash
./rclone copy --filter-from ../../settings/decky-cloud-save/sync_paths_filter.txt / backend:decky-cloud-save --copy-links --dry-run
```
82 changes: 64 additions & 18 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
rclone_bin = plugin_dir / "rclone"
rclone_cfg = config_dir / "rclone.conf"

cfg_syncpath_file = config_dir / "sync_paths.txt"
cfg_syncpath_includes_file = config_dir / "sync_paths.txt"
cfg_syncpath_excludes_file = config_dir / "sync_paths_excludes.txt"
cfg_syncpath_filter_file = config_dir / "sync_paths_filter.txt"
cfg_property_file = config_dir / "plugin.properties"

logger = logging.getLogger()
Expand Down Expand Up @@ -41,6 +43,26 @@ async def _kill_previous_spawn(process: asyncio.subprocess.Process):
await asyncio.sleep(0.1) # Give time for OS to clear up the port


def _regenerate_filter_file():
with open(cfg_syncpath_includes_file, 'r') as f:
includes = f.readlines()
with open(cfg_syncpath_excludes_file, 'r') as f:
excludes = f.readlines()

with open(cfg_syncpath_filter_file, 'w') as f:
f.write("# Last line of this file MUST be '- **' (exclude the rest) as otherwise you will start backing up the entire steam deck!\n")
f.write("# If you are editing this file manually, make sure to not use the UI file picker, as the saves done there will overwrite.\n")
f.write("# Examples: https://rclone.org/filtering/#examples\n")
f.write("\n")

for exclude in excludes:
f.write(f"- {exclude}")
f.write("\n")
for include in includes:
f.write(f"+ {include}")
f.write("\n")
f.write("- **\n")

class Plugin:
current_spawn = None
current_sync = None
Expand Down Expand Up @@ -82,7 +104,7 @@ async def get_backend_type(self):

async def sync_now(self):
logger.debug("Executing: sync_now()")
self.current_sync = await asyncio.subprocess.create_subprocess_exec(rclone_bin, *["copy", "--include-from", cfg_syncpath_file, "/", "backend:decky-cloud-save", "--copy-links"])
self.current_sync = await asyncio.subprocess.create_subprocess_exec(rclone_bin, *["copy", "--filter-from", cfg_syncpath_filter_file, "/", "backend:decky-cloud-save", "--copy-links"])

async def sync_now_probe(self):
logger.debug("Executing: sync_now_probe()")
Expand All @@ -93,52 +115,72 @@ async def sync_now_probe(self):

#

async def get_syncpaths(self):
async def get_syncpaths(self, file: str):
logger.debug("Executing: get_syncpaths()")
with open(cfg_syncpath_file, "r") as f:

file = cfg_syncpath_excludes_file if file == "excludes" else cfg_syncpath_includes_file

with open(file, "r") as f:
return f.readlines()

async def test_syncpath(self, path: str):
logger.debug("Executing: test_syncpath(%s)", path)
if not path.endswith("/**"):

if path.endswith("/**"):
scan_single_dir = False
path = path[:-3]
elif path.endswith("/*"):
scan_single_dir = True
path = path[:-2]
else:
return int(Path(path).is_file())

count = 0
for root, os_dirs, os_files in os.walk(path[:-3], followlinks=True):
for root, os_dirs, os_files in os.walk(path, followlinks=True):
logger.debug("%s %s %s", root, os_dirs, os_files)
count += len(os_files)
if count > 9000:
return "9000+"
if scan_single_dir:
break

return count

async def add_syncpath(self, path: str):
logger.debug("Executing: add_syncpath(%s)", path)
logger.info("Adding Path to Sync: '%s'", path)
async def add_syncpath(self, path: str, file: str):
logger.debug("Executing: add_syncpath(%s, %s)", path, file)
logger.info("Adding Path to Sync: '%s', %s", path, file)

with open(cfg_syncpath_file, "r") as f:
file = cfg_syncpath_excludes_file if file == "excludes" else cfg_syncpath_includes_file

with open(file, "r") as f:
lines = f.readlines()
for line in lines:
if line.strip("\n") == path:
return

lines += [f"{path}\n"]

with open(cfg_syncpath_file, "w") as f:
with open(file, "w") as f:
for line in lines:
f.write(line)

async def remove_syncpath(self, path: str):
logger.debug("Executing: remove_syncpath(%s)", path)
logger.info("Removing Path from Sync: '%s'", path)
_regenerate_filter_file()

async def remove_syncpath(self, path: str, file: str):
logger.debug("Executing: remove_syncpath(%s, %s)", path, file)
logger.info("Removing Path from Sync: '%s', %s", path, file)

with open(cfg_syncpath_file, "r") as f:
file = cfg_syncpath_excludes_file if file == "excludes" else cfg_syncpath_includes_file

with open(file, "r") as f:
lines = f.readlines()
with open(cfg_syncpath_file, "w") as f:
with open(file, "w") as f:
for line in lines:
if line.strip("\n") != path:
f.write(line)

_regenerate_filter_file()

#

async def get_config(self):
Expand Down Expand Up @@ -176,8 +218,12 @@ async def _main(self):
if not config_dir.is_dir():
os.makedirs(config_dir, exist_ok=True)

if not cfg_syncpath_file.is_file():
cfg_syncpath_file.touch()
if not cfg_syncpath_includes_file.is_file():
cfg_syncpath_includes_file.touch()
if not cfg_syncpath_excludes_file.is_file():
cfg_syncpath_excludes_file.touch()
if not cfg_syncpath_filter_file.is_file():
_regenerate_filter_file()
if not cfg_property_file.is_file():
cfg_property_file.touch()

Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "decky-cloud-save",
"version": "1.1.2",
"version": "1.2.0",
"description": "Manage cloud saves for games that do not support it in [current year].",
"scripts": {
"build": "shx rm -rf dist && rollup -c",
Expand Down Expand Up @@ -34,16 +34,16 @@
"@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-typescript": "^8.5.0",
"@types/react": "16.14.0",
"@types/webpack": "^5.28.0",
"@types/webpack": "^5.28.1",
"rollup": "^2.79.1",
"rollup-plugin-import-assets": "^1.1.1",
"shx": "^0.3.4",
"tslib": "^2.5.0",
"typescript": "^4.9.5"
},
"dependencies": {
"decky-frontend-lib": "^3.19.1",
"react-icons": "^4.7.1"
"decky-frontend-lib": "^3.19.2",
"react-icons": "^4.8.0"
},
"pnpm": {
"peerDependencyRules": {
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Decky Cloud Save",
"author": "GedasFX",
"flags": ["debug"],
"flags": [],
"publish": {
"tags": ["backup", "cloud", "rclone"],
"description": "Manage cloud saves for games that do not support it in [current year].",
Expand Down
Loading

0 comments on commit df33b81

Please sign in to comment.