Last active
May 25, 2020 19:37
-
-
Save pklaus/2a7f533709ef6bee7029 to your computer and use it in GitHub Desktop.
One-way sync to an SMB/CIFS server – Currently only a flat structure of files in the source directory is supported
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" One-way sync to an SMB/CIFS server (needs pysmb) """ | |
import logging, socket, os, sys, math | |
from datetime import datetime | |
try: | |
from smb.SMBConnection import SMBConnection | |
from smb.base import NotReadyError, NotConnectedError | |
from nmb.NetBIOS import NetBIOS | |
PYSMB = True | |
except ImportError: | |
PYSMB = False | |
def props_SharedFile(sf): | |
return { | |
'ctime': datetime.utcfromtimestamp(sf.create_time), | |
'mtime': datetime.utcfromtimestamp(sf.last_write_time), | |
'size': math.ceil(sf.file_size/1024.0), | |
'file': not sf.isDirectory, | |
'name': sf.filename, | |
} | |
def props_LocalFile(path): | |
return { | |
'mtime': datetime.utcfromtimestamp(os.path.getmtime(path)), | |
'ctime': datetime.utcfromtimestamp(os.path.getctime(path)), | |
'size': math.ceil(os.path.getsize(path)/1024.0), | |
'file': os.path.isfile(path), | |
'name': os.path.basename(path), | |
} | |
def info_for_SharedFile(sf): | |
props = props_SharedFile(sf) | |
props['kind'] = 'file' if props['file'] else ' dir' | |
tpl = "Created: {ctime}, Last change: {mtime}, Size: {size} KiB, Kind: {kind}, Name: {name}" | |
return tpl.format(**props) | |
def main(): | |
if not PYSMB: | |
print('Could not find the dependency pysmb. Please install it first using `pip install pysmb`.') | |
sys.exit(127) | |
import argparse | |
parser = argparse.ArgumentParser(description=__doc__) | |
parser.add_argument('--server_ip', '-i', help='SMB/CIFS Server IP', required=True) | |
parser.add_argument('--share', '-s', help='Name of the share', required=True) | |
parser.add_argument('--domain', '-d') | |
parser.add_argument('--username', '-u') | |
parser.add_argument('--password', '-p') | |
parser.add_argument('--server_name', '-n', help='SMB/CIFS Server Name (optional)') | |
parser.add_argument('--verbose', '-v', action='store_true', help='Make this tool more verbose') | |
parser.add_argument('from_path', help='Local path to sync to the share') | |
parser.add_argument('to_path', help='Path in the share to sync to') | |
args = parser.parse_args() | |
logging.basicConfig(level = (logging.DEBUG if args.verbose else logging.WARNING)) | |
logger = logging.getLogger('SMB.SMBConnection') | |
try: | |
nb = NetBIOS(broadcast=True) | |
if not args.server_name: | |
args.server_name = nb.queryIPForName(args.server_ip)[0] | |
if not args.server_ip: | |
args.server_ip = nb.queryName(args.server_name) | |
conn = SMBConnection(args.username, args.password, socket.gethostname(), args.server_name, use_ntlm_v2=True, domain=args.domain) | |
conn.connect(args.server_ip, 139, timeout=30) | |
except (NotReadyError, NotConnectedError) as e: | |
logger.error('Could not connect: ' + e.__class__.__name__) | |
sys.exit(2) | |
except Exception as e: | |
logger.error('Could not connect:') | |
logger.error(e.__class__.__name__ + " " + str(e)) | |
sys.exit(2) | |
try: | |
## Doesn't work on Python3: | |
#conn.getAttributes(args.share, args.to_path) | |
filelist = conn.listPath(args.share, args.to_path) | |
except: | |
conn.createDirectory(args.share, args.to_path) | |
filelist = conn.listPath(args.share, args.to_path) | |
filelist = [f for f in filelist if f.filename not in ['.', '..']] | |
filename_index = dict() | |
logger.info("Files currently stored on the server:") | |
for sf in filelist: | |
logger.info(info_for_SharedFile(sf)) | |
filename_index[sf.filename] = sf | |
# Loop over local files | |
logger.info("Files in local folder to sync:") | |
for fn in os.listdir(args.from_path): | |
if not os.path.isfile(os.path.join(args.from_path, fn)): | |
logger.info('Skipping local folder: {}'.format(fn)) | |
continue | |
if fn not in filename_index: | |
logger.info("{} not yet on the server. uploading...".format(fn)) | |
conn.storeFile(args.share, os.path.join(args.to_path, fn), open(os.path.join(args.from_path, fn), 'rb')) | |
elif props_SharedFile(filename_index[fn])['size'] != props_LocalFile(os.path.join(args.from_path, fn))['size']: | |
logger.info("{} outdated or incomplete on the server. updating...".format(fn)) | |
conn.storeFile(args.share, os.path.join(args.to_path, fn), open(os.path.join(args.from_path, fn), 'rb')) | |
else: | |
logger.info("{} already on the server.".format(fn)) | |
if __name__ == "__main__": | |
main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment