Initial commit
This commit is contained in:
199
main.py
Executable file
199
main.py
Executable file
@@ -0,0 +1,199 @@
|
||||
import subprocess
|
||||
import configparser
|
||||
import os
|
||||
import argparse
|
||||
import time
|
||||
import logging
|
||||
|
||||
from BetterADBSync import sync_with_options
|
||||
|
||||
# debug vs standard logging
|
||||
DEBUG = False
|
||||
|
||||
if DEBUG:
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="[%(asctime)s][%(levelname)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="[%(asctime)s][%(levelname)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
filename="sync.log",
|
||||
filemode="a"
|
||||
)
|
||||
|
||||
|
||||
def parse_command_output(output: str) -> dict:
|
||||
lines = output.split(os.linesep)
|
||||
result = {}
|
||||
i = lines.index("[INFO] SYNCING")
|
||||
|
||||
trees = {
|
||||
'deleted': [
|
||||
'Deleting delete tree',
|
||||
'Empty delete tree',
|
||||
'Deleting non-excluded-supporting destination unaccounted tree'
|
||||
],
|
||||
'copied': [
|
||||
'Copying copy tree',
|
||||
'Empty copy tree'
|
||||
]
|
||||
}
|
||||
|
||||
current_tree = ''
|
||||
|
||||
for line in lines[i + 2:]:
|
||||
actual_line = line[6:].strip()
|
||||
for tree, matches in trees.items():
|
||||
if actual_line in matches:
|
||||
current_tree = tree
|
||||
result[current_tree] = []
|
||||
break
|
||||
else:
|
||||
if current_tree:
|
||||
if actual_line == '':
|
||||
current_tree = ''
|
||||
continue
|
||||
if actual_line.startswith('Removing '):
|
||||
actual_line = actual_line[9:]
|
||||
result[current_tree].append(actual_line)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def traverse_tree(tree: dict, level: int = 0):
|
||||
"""Traverse a tree and print it nicely. Also keep a count of the number of files in the tree."""
|
||||
count = 0
|
||||
for key, value in tree.items():
|
||||
if isinstance(value, dict):
|
||||
logging.info(f"{' ' * level}{key}/")
|
||||
count += traverse_tree(value, level + 1)
|
||||
else:
|
||||
logging.info(f"{' ' * level}{key}")
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Daemon for syncing files between a computer and an Android device.")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Perform a dry run without actually "
|
||||
"syncing files. (Only runs once)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Read config file
|
||||
if not os.path.exists("config.ini"):
|
||||
# Create a new config file
|
||||
config = configparser.ConfigParser()
|
||||
config["Global"] = {
|
||||
"adb_path": "adb",
|
||||
"device": ""
|
||||
}
|
||||
|
||||
config["__example_Path_1"] = {
|
||||
"type": "push",
|
||||
"source": "C:/path/to/source",
|
||||
"destination": "/path/to/destination",
|
||||
"exclude_1": "path/to/exclude",
|
||||
"exclude_2": "path/to/exclude",
|
||||
"delete_files_in_dest": "no"
|
||||
}
|
||||
|
||||
print("New configuration file created. Please edit the file to set paths and other configurations.")
|
||||
|
||||
with open("config.ini", "w") as configfile:
|
||||
config.write(configfile)
|
||||
return
|
||||
|
||||
else:
|
||||
config = configparser.ConfigParser()
|
||||
config.read("config.ini")
|
||||
|
||||
adb_path = config["Global"]["adb_path"]
|
||||
device = config["Global"]["device"]
|
||||
|
||||
# First run adb, check if server is running
|
||||
result = subprocess.run([adb_path, "start-server"])
|
||||
if result.returncode != 0:
|
||||
logging.error("Could not start ADB server. Exiting...")
|
||||
return
|
||||
|
||||
run = True
|
||||
first_run = True
|
||||
|
||||
while run:
|
||||
# Get the list of devices
|
||||
result = subprocess.run([adb_path, "devices"], stdout=subprocess.PIPE)
|
||||
output = result.stdout.decode("utf-8")
|
||||
devices = output.split(os.linesep)[1:-2]
|
||||
devices = [device.split("\t")[0] for device in devices]
|
||||
if len(devices) == 0:
|
||||
# Device is not yet connnected
|
||||
logging.info("No device connected. Retrying in 60 seconds.")
|
||||
time.sleep(60)
|
||||
continue
|
||||
if device and device not in devices:
|
||||
time.sleep(60)
|
||||
continue
|
||||
if not device:
|
||||
device = devices[0]
|
||||
|
||||
if first_run:
|
||||
logging.info(f"Device connected: {device}")
|
||||
logging.info(f"Found {len(config.sections()) - 1} path{'s' if len(config.sections()) > 2 else ''} to sync.")
|
||||
logging.info('')
|
||||
|
||||
# Sync files
|
||||
for section in config.sections():
|
||||
if section == "Global" or section.startswith("__"):
|
||||
continue
|
||||
if first_run:
|
||||
logging.info(f"Syncing {section}...")
|
||||
type = config[section]["type"]
|
||||
source = config[section]["source"]
|
||||
destination = config[section]["destination"]
|
||||
exclude = [config[section][key] for key in config[section] if key.startswith("exclude")]
|
||||
delete_files_in_dest = config[section].getboolean("delete_files_in_dest")
|
||||
|
||||
options = {
|
||||
'adb_options': {'s': device},
|
||||
}
|
||||
|
||||
if args.dry_run:
|
||||
options['dry_run'] = True
|
||||
|
||||
if exclude:
|
||||
options['exclude'] = exclude
|
||||
|
||||
if delete_files_in_dest:
|
||||
options['delete'] = True
|
||||
|
||||
options['show_progress'] = True
|
||||
options['direction'] = type
|
||||
options['source'] = source
|
||||
options['dest'] = destination
|
||||
|
||||
copied, deleted = sync_with_options(**options)
|
||||
|
||||
if copied:
|
||||
count = traverse_tree(copied)
|
||||
logging.info(f"Copied {count} files to {destination}\n")
|
||||
|
||||
if deleted and delete_files_in_dest:
|
||||
count = traverse_tree(deleted)
|
||||
logging.info(f"Deleted {count} files from {destination}\n")
|
||||
|
||||
if args.dry_run:
|
||||
run = False
|
||||
else:
|
||||
if first_run:
|
||||
logging.info("Entering loop and waiting for file changes")
|
||||
first_run = False
|
||||
time.sleep(300)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user