Skip to content

Instantly share code, notes, and snippets.

@pklaus
Last active May 25, 2020 19:37
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pklaus/2a7f533709ef6bee7029 to your computer and use it in GitHub Desktop.
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
#!/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