Skip to content

Commit

Permalink
Merge branch 'feature-afterWatch'
Browse files Browse the repository at this point in the history
  • Loading branch information
NoxArt committed Sep 2, 2012
2 parents e9a590e + d54c598 commit a0d446d
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 15 deletions.
42 changes: 40 additions & 2 deletions FTPSync.py
Expand Up @@ -50,7 +50,7 @@
# FTPSync libraries
from ftpsyncwrapper import CreateConnection, TargetAlreadyExists
from ftpsyncprogress import Progress
from ftpsyncfiles import getFolders, findFile, getFiles, formatTimestamp
from ftpsyncfiles import getFolders, findFile, getFiles, formatTimestamp, gatherMetafiles, getChangedFiles


# ==== Initialization and optimization =====================================================
Expand Down Expand Up @@ -316,7 +316,7 @@ def verifyConfig(config):
if type(config) is not dict:
return "Config is not a {dict} type"

keys = ["username", "password", "private_key", "private_key_pass", "path", "tls", "upload_on_save", "port", "timeout", "ignore", "check_time", "download_on_open", "upload_delay"]
keys = ["username", "password", "private_key", "private_key_pass", "path", "tls", "upload_on_save", "port", "timeout", "ignore", "check_time", "download_on_open", "upload_delay", "after_save_watch"]

for key in keys:
if key not in config:
Expand Down Expand Up @@ -358,6 +358,9 @@ def verifyConfig(config):
if type(config['upload_delay']) is not int and type(config['upload_delay']) is not long:
return "Config entry 'upload_delay' must be integer or long, " + unicode(type(config['upload_delay'])) + " given"

if config['after_save_watch'] is not None and type(config['after_save_watch']) is not list:
return "Config entry 'after_save_watch' must be null or list, " + unicode(type(config['after_save_watch'])) + " given"

if type(config['port']) is not int and type(config['port']) is not long:
return "Config entry 'port' must be an integer or long, " + unicode(type(config['port'])) + " given"

Expand Down Expand Up @@ -724,6 +727,18 @@ def __init__(self, file_path, config_file_path, progress=None, onSave=False, dis
SyncCommandTransfer.__init__(self, file_path, config_file_path, progress, onSave, disregardIgnore, whitelistConnections)

self.delayed = False
self.afterwatch = None


def scanWatched(self, event, name, properties):
root = os.path.dirname(self.config_file_path)
watch = properties['after_save_watch']
self.afterwatch[event][name] = {}

if type(watch) is list and len(watch) > 0 and properties['upload_delay'] > 0:
for folder, filepattern in watch:
self.afterwatch[event][name] = dict(self.afterwatch[event][name].items() + gatherMetafiles(filepattern, os.path.join(root, folder)).items())


def execute(self):
if self.progress is not None:
Expand All @@ -737,6 +752,21 @@ def execute(self):
printMessage("Cancelling " + unicode(self.__class__.__name__) + ": zero connections apply")
return

# afterwatch
if self.onSave is True:
self.afterwatch = {
'before': {},
'after': {}
}

index = -1

for name in self.config['connections']:
index += 1
self.scanWatched('before', name, self.config['connections'][name])

print self.afterwatch

usingConnections.append(self.config_hash)
stored = []
index = -1
Expand Down Expand Up @@ -767,6 +797,14 @@ def action():
scheduledUploads.pop(self.file_path)

if self.delayed is True:
# afterwatch
self.afterwatch['after'][name] = {}
self.scanWatched('after', name, self.config['connections'][name])
changed = getChangedFiles(self.afterwatch['before'][name], self.afterwatch['after'][name])
for change in changed:
change = change.getPath()
SyncCommandUpload(change, getConfigFile(change), None, False, True, [name]).execute()

self.delayed = False
self.__del__()

Expand Down
10 changes: 8 additions & 2 deletions README.md
Expand Up @@ -8,6 +8,7 @@ Recently new
* Able to upload solely folders as well
* Full relative path in status bar
* Added protection from overwriting remote file by renaming
* AfterSaveWatch for uploading changed files

What's there for you?
* Multiple named upload targets
Expand Down Expand Up @@ -52,9 +53,14 @@ Format:
tls: {bool=false}, // set true to use secured transfer, recommended! (server needs to support)
passive: {bool=true}, // whether to use passive or active connection
timeout: {int=30}, // seconds to invalidate the cached connection
ignore: {null|string} // regular expression, matched against file path - not applied for downloading
ignore: {null|string}. // regular expression, matched against file path - not applied for downloading

line_separator: {string=\n}. // line separator for text files used in your project, usually \n, can be \r\n

after_save_watch: {null|list<list<subfolder, filepatter>>=null} // after save watch
// example: [ [ "code/assets/css", "*.css" ], [ "code/assets/", "*.jpg, *.png, *.gif" ] ]
// more in Wiki

line_separator: {string=\n} // line separator for text files used in your project, usually \n, can be \r\n
} //,
// <connection2_name>: { ... }
}
Expand Down
11 changes: 9 additions & 2 deletions ftpsync.default-settings
Expand Up @@ -68,8 +68,15 @@
// chmod value for directories created on remote server by FTPSync
//"default_folder_permissions": "755",

//line separator for text files
//"line_separator": "\n"
// line separator for text files
//"line_separator": "\n",

// list of lists with pathnames and filenames to folders to be watched for change in between delay (upload_delay)
//
// example:
// after_save_watch: [ [ "code/assets/css", "*.css" ], [ "code/assets/", "*.jpg, *.png, *.gif" ] ]
//
//"after_save_watch": []

}
}
2 changes: 2 additions & 0 deletions ftpsync.sublime-settings
Expand Up @@ -26,6 +26,8 @@
"default_folder_permissions": "755",
"line_separator": "\n",

"after_save_watch": null,

"debug_extras": {
"print_list_result": false,
"dump_config_load": false
Expand Down
103 changes: 94 additions & 9 deletions ftpsyncfiles.py
Expand Up @@ -32,6 +32,8 @@
# Python's built-in libraries
import os
import datetime
import fnmatch
import re


# ==== Initialization and optimization =====================================================
Expand Down Expand Up @@ -64,15 +66,19 @@
# A file representation with helper methods
class Metafile:

def __init__(self, name, isDir, lastModified, filesize):
def __init__(self, name, isDir, lastModified, filesize, path=None):
self.name = name
self.isDir = bool(isDir)
self.lastModified = float(lastModified)
self.filesize = float(filesize)
self.path = path

def getName(self):
return self.name

def getPath(self):
return self.path

def isDirectory(self):
return self.isDir

Expand All @@ -85,17 +91,48 @@ def getLastModifiedFormatted(self, format='%Y-%m-%d %H:%M'):
def getFilesize(self):
return self.filesize

def isNewerThan(self, file_path):
if os.path.exists(file_path) is False:
return True
def isNewerThan(self, compared_file):
if type(compared_file) is str or type(compared_file) is unicode:
if os.path.exists(compared_file) is False:
return False

return self.lastModified > os.path.getmtime(file_path)
lastModified = os.path.getmtime(compared_file)
elif isinstance(compared_file, Metafile):
lastModified = compared_file.getLastModified()
else:
raise TypeError("Compared_file must be either string (file_path) or Metafile instance")

def isDifferentSizeThan(self, file_path):
if os.path.exists(file_path) is False:
return True
return self.lastModified > lastModified

def isDifferentSizeThan(self, compared_file):
if type(compared_file) is str or type(compared_file) is unicode:
if os.path.exists(compared_file) is False:
return False

lastModified = os.path.getsize(compared_file)
elif isinstance(compared_file, Metafile):
lastModified = compared_file.getLastModified()
else:
raise TypeError("Compared_file must be either string (file_path) or Metafile instance")

return self.filesize != os.path.getsize(compared_file)



# Converts file_path to Metafile
#
# @type file_path: string
#
# @return Metafile
def fileToMetafile(file_path):
name = os.path.basename(file_path)
path = file_path
isDir = os.path.isdir(file_path)
lastModified = os.path.getmtime(file_path)
filesize = os.path.getsize(file_path)

return Metafile(name, isDir, lastModified, filesize, path)

return self.filesize != os.path.getsize(file_path)


# Returns a timestamp formatted for humans
Expand Down Expand Up @@ -180,11 +217,59 @@ def getFiles(paths, getConfigFile):

for target in paths:
if target not in fileNames:
fileNames.append(target)
files.append([target, getConfigFile(target)])

return files



# Goes through paths using glob and returns list of Metafiles
#
# @type pattern: string
# @param pattern: glob-like filename pattern
# @type root: string
# @param root: top searched directory
#
# @return list<Metafiles>
def gatherMetafiles(pattern, root):
if pattern is None:
return []

result = {}
file_names = []

for subroot, dirnames, filenames in os.walk(root):
for filename in fnmatch.filter(filenames, pattern):
target = os.path.join(subroot, filename)

if target not in file_names:
file_names.append(target)
result[target] = fileToMetafile(target)

for folder in dirnames:
result = dict(result.items() + gatherMetafiles(pattern, os.path.join(root, folder)).items())

return result



# Returns difference using lastModified between file dicts
#
# @type metafilesBefore: dict
# @type metafilesAfter: dict
#
# @return list<Metafiles>
def getChangedFiles(metafilesBefore, metafilesAfter):
changed = []
for file_path in metafilesAfter:
if file_path in metafilesBefore and metafilesAfter[file_path].isNewerThan(metafilesBefore[file_path]):
changed.append(metafilesAfter[file_path])

return changed



# Guesses whether given file is textual or not
#
# @type file_path: string
Expand Down

0 comments on commit a0d446d

Please sign in to comment.