├── smartcopy.egg-info ├── not-zip-safe ├── dependency_links.txt ├── requires.txt ├── top_level.txt ├── SOURCES.txt └── PKG-INFO ├── dist ├── smartcopy-0.1.tar.gz ├── smartcopy-0.2.tar.gz ├── smartcopy-0.5.tar.gz └── smartcopy-0.6.tar.gz ├── smartutils ├── __init__.pyc └── __init__.py ├── .idea ├── scopes │ └── scope_settings.xml ├── encodings.xml ├── vcs.xml ├── other.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── libraries │ └── sass_stdlib.xml ├── misc.xml ├── testrunner.xml ├── modules.xml ├── smartcopy.iml └── workspace.xml ├── setup.py ├── README.md └── smartcopy ├── smartcopy └── smartcopyd /smartcopy.egg-info/not-zip-safe: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /smartcopy.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /smartcopy.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | watchdog>=0.7.1 -------------------------------------------------------------------------------- /smartcopy.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | smartutils 2 | -------------------------------------------------------------------------------- /dist/smartcopy-0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarchak/smartcopy/HEAD/dist/smartcopy-0.1.tar.gz -------------------------------------------------------------------------------- /dist/smartcopy-0.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarchak/smartcopy/HEAD/dist/smartcopy-0.2.tar.gz -------------------------------------------------------------------------------- /dist/smartcopy-0.5.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarchak/smartcopy/HEAD/dist/smartcopy-0.5.tar.gz -------------------------------------------------------------------------------- /dist/smartcopy-0.6.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarchak/smartcopy/HEAD/dist/smartcopy-0.6.tar.gz -------------------------------------------------------------------------------- /smartutils/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarchak/smartcopy/HEAD/smartutils/__init__.pyc -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/other.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/libraries/sass_stdlib.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/testrunner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /smartcopy.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | setup.py 2 | smartcopy/smartcopy 3 | smartcopy/smartcopyd 4 | smartcopy.egg-info/PKG-INFO 5 | smartcopy.egg-info/SOURCES.txt 6 | smartcopy.egg-info/dependency_links.txt 7 | smartcopy.egg-info/not-zip-safe 8 | smartcopy.egg-info/requires.txt 9 | smartcopy.egg-info/top_level.txt 10 | smartutils/__init__.py -------------------------------------------------------------------------------- /.idea/smartcopy.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /smartcopy.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: smartcopy 3 | Version: 0.6 4 | Summary: Intelligent layer on top of any cloud storage provider like Dropbox/Box/Google Drive 5 | Home-page: http://github.com/sarchak/smartcopy 6 | Author: Shrikar Archak 7 | Author-email: shrikar84@gmail.com 8 | License: MIT 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='smartcopy', 4 | version='0.6', 5 | description='Intelligent layer on top of any cloud storage provider like Dropbox/Box/Google Drive', 6 | url='http://github.com/sarchak/smartcopy', 7 | author='Shrikar Archak', 8 | author_email='shrikar84@gmail.com', 9 | license='MIT', 10 | install_requires=['watchdog>=0.7.1'], 11 | scripts=['smartcopy/smartcopy','smartcopy/smartcopyd'], 12 | packages=["smartutils"], 13 | zip_safe=False) 14 | -------------------------------------------------------------------------------- /smartutils/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'shrikar' 2 | from os.path import expanduser 3 | import os 4 | def get_config(): 5 | home = expanduser("~") 6 | return home+"/.smartcopy/config" 7 | 8 | def get_db(): 9 | home = expanduser("~") 10 | return home+"/.smartcopy/database" 11 | 12 | 13 | def reload_config(): 14 | fd = open(get_config(),"r") 15 | line = fd.readline() 16 | src = line.split("=")[1].strip("\n") 17 | line = fd.readline() 18 | dest = line.split("=")[1].strip("\n") 19 | fd.close() 20 | if(not os.path.exists(src)): 21 | os.system("mkdir -p "+src) 22 | if(not os.path.exists(dest)): 23 | os.system("mkdir -p "+dest) 24 | 25 | return (src,dest) 26 | 27 | def get_patterns(): 28 | fd = open(get_db(),"r") 29 | tmp = [] 30 | for pattern in fd.readlines(): 31 | tmp.append(pattern.strip("\n")) 32 | fd.close() 33 | return "|".join(tmp) 34 | 35 | def quote(argument): 36 | return argument.replace(" ","\ ").replace("*","\*").replace("$","\$").replace("^","\^") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | smartcopy 2 | ========= 3 | 4 | Intelligent layer on top of existing cloud storage 5 | 6 | ### Requirements 7 | 8 | * Unix based OS with fork support and support for watching filesystem changes through inotify, FSEvents or kqueue. 9 | * python 2.7 and above 10 | 11 | ### Design 12 | 13 | I followed a similar method to .gitignore and hence decided to have a list of all the pattern that need to be ignored from syncing 14 | 15 | #### Example 16 | 17 | * .*.jar : Ignore all the files containing .jar 18 | * .class$ : Ignore all the files ending with .class 19 | * ^Bingo : Ignore all the files starting with Bingo 20 | 21 | For more information on using regular expression please check the python regex documentation. 22 | 23 | ### Components 24 | 25 | * ) smartcopyd : SmartCopy Daemon 26 | smartcopydaemon monitors for changes to a directory , filter the files according to the ignore patterns and sync's to the cloud storage. 27 | 28 | * ) smartcopy : SmartCopy Client 29 | smartcopy allows you to change the config file and modify any ignore pattern rules. 30 | 31 | [Python regex](http://docs.python.org/2/library/re.html) 32 | 33 | 34 | ### Installation 35 | 36 | #### Method 1 ( For using the latest stable branch) 37 | 38 | * sudo easy_install smartcopy 39 | 40 | #### Method 2 ( For current development branch) 41 | * git clone https://github.com/sarchak/smartcopy.git 42 | * cd smartcopy 43 | * sudo python setup.py install 44 | 45 | ### Starting the daemon 46 | 47 |

48 | Shrikars-MacBook-Pro:~ shrikar$ smartcopyd
49 | Shrikars-MacBook-Pro:~ shrikar$ smartcopy
50 | /Users/shrikar/.smartcopy
51 | Starting SmartCopy Engine...
52 | smartcopy> add .*.jpg
53 | Adding this pattern to ignore path :  .*.jpg
54 | smartcopy> add .*.pdf
55 | Adding this pattern to ignore path :  .*.pdf
56 | smartcopy> add .*.jar
57 | Adding this pattern to ignore path :  .*.jar
58 | smartcopy>
59 | 
60 | 61 | 62 | The above command will create a directory called SmartCopy in your home folder feel free to drag it to the finder sidebar or where ever its convenient for you to copy files. 63 | 64 | ### Important : While syncing files DO NOT COPY OR MOVE files directly to the dropbox folder instead copy/move to SmartCopy folder which will filter the files and sync's them to the dropbox folder. 65 | 66 | [More Info](http://shrikar.com/blog/2014/02/21/smartcopy-intelligent-layer-on-top-of-existing-cloud-storage/) 67 | -------------------------------------------------------------------------------- /smartcopy/smartcopy: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | 3 | __author__ = 'shrikar' 4 | from cmd import Cmd 5 | from os.path import expanduser 6 | import os 7 | from smartutils import get_config 8 | from smartutils import get_db 9 | from smartutils import reload_config 10 | import subprocess 11 | import sys 12 | class MyPrompt(Cmd): 13 | 14 | def __init__(self): 15 | Cmd.__init__(self) 16 | self.data = [] 17 | fd = open(get_db(),"r") 18 | for pattern in fd.readlines(): 19 | self.data.append(pattern.strip("\n")) 20 | fd.close() 21 | self.index = 1 22 | 23 | def savefile(self): 24 | home = expanduser("~") 25 | fd = open(get_db(),"w") 26 | for pattern in self.data: 27 | fd.write(pattern+"\n") 28 | fd.close() 29 | 30 | def do_change_config(self,args): 31 | """In case you want to change the path of the location which need to be watched or the destination 32 | path use this command. Please note after this you need to restart the smartcopyd ( Smart copy daemon).""" 33 | fd = open(get_config()+".bk","w") 34 | source = raw_input("Enter local directory: ") 35 | destination = raw_input("Enter Dropbox/Box/Google mounted path: ") 36 | fd.write("source="+source+"\n") 37 | fd.write("destination="+destination+"\n") 38 | fd.close() 39 | os.system("mv "+get_config()+".bk " + get_config()) 40 | os.system("pkill -f smartcopyd") 41 | print("=================================================================") 42 | print("!!!! Important information !!!!") 43 | print "Please restart the smartcopyd.\nExample : \nsmartcopyd" 44 | print("=================================================================") 45 | sys.exit(0) 46 | 47 | def do_display_config(self,args): 48 | """Displays the current source and the destination path.""" 49 | src,dest = reload_config() 50 | print "Local directory : " + src 51 | print "Dropbox/Box/Google mounted path : " + dest 52 | 53 | def do_add(self, args): 54 | """Add a file pattern to be ignored.""" 55 | if(args==""): 56 | print "Please provide the pattern. " 57 | print "Example: add *.jar ( For ignore any file ending with jar ) " 58 | return; 59 | print "Adding this pattern to ignore path : %s" % args 60 | self.data.append(args) 61 | self.savefile() 62 | 63 | def do_list(self,args): 64 | """List existing patterns that are ignored.""" 65 | self.index = 1 66 | if(len(self.data) == 0): 67 | print "No pattern listed in the database." 68 | 69 | for pattern in self.data: 70 | print str(self.index) +" ) " + pattern 71 | self.index += 1 72 | 73 | def do_del(self,args): 74 | """Delete an existing pattern that was ignored.""" 75 | if(int(args) > len(self.data)): 76 | return 77 | del self.data[int(args)-1] 78 | self.do_list(0) 79 | self.savefile() 80 | 81 | def do_quit(self, args): 82 | """Quits the program.""" 83 | print "Quitting." 84 | raise SystemExit 85 | 86 | 87 | if __name__ == '__main__': 88 | home = expanduser("~") 89 | smartdir = home+"/.smartcopy" 90 | if(not os.path.exists(smartdir)): 91 | os.system("mkdir -p " + smartdir) 92 | if(not os.path.exists(get_config())): 93 | fd = open(get_config(),"w") 94 | fd.write("source="+home+"/SmartCopy\n") 95 | fd.write("destination="+home+"/Dropbox\n") 96 | fd.close() 97 | if(not os.path.exists(get_db())): 98 | subprocess.call(["touch ",get_db()]) 99 | 100 | prompt = MyPrompt() 101 | prompt.prompt = 'smartcopy> ' 102 | prompt.cmdloop('Starting SmartCopy Engine...') 103 | -------------------------------------------------------------------------------- /smartcopy/smartcopyd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import time 3 | from watchdog.observers import Observer 4 | from watchdog.events import FileSystemEventHandler 5 | import re 6 | import os 7 | import sys 8 | import subprocess 9 | from Queue import Queue 10 | from os.path import expanduser 11 | from smartutils import get_config 12 | from smartutils import get_db 13 | from smartutils import reload_config 14 | from smartutils import get_patterns 15 | from smartutils import quote 16 | 17 | class MyHandler(FileSystemEventHandler): 18 | def __init__(self,src,dest,dirstocreate,filestocopy,filestodelete): 19 | self.src = src 20 | self.dest = dest 21 | self.dirstocreate = dirstocreate 22 | self.filestocopy = filestocopy 23 | self.filestodelete = filestodelete 24 | self.patterns = re.compile(get_patterns()) 25 | 26 | 27 | def on_created(self, event): 28 | ## Ignore the created event for the backup file generated 29 | ## during the change config phase 30 | 31 | if(event.src_path == get_config()+".bk"): 32 | return; 33 | 34 | ## If there is not pattern match for the files to be ignored or there are no rules 35 | ## in the database add to the files to be copied case. 36 | if len(self.patterns.findall(event.src_path)) == 0 or os.path.getsize(get_db()) == 0: 37 | if(event.is_directory): 38 | self.dirstocreate.put(event.src_path) 39 | else: 40 | self.filestocopy.put(event.src_path) 41 | 42 | 43 | def on_moved(self, event): 44 | ## Delegate to the on created 45 | self.on_created(event) 46 | 47 | def on_deleted(self, event): 48 | ## Add the file to the list of files to be deleted 49 | self.filestodelete.put(event.src_path) 50 | 51 | def on_modified(self, event): 52 | ## If the file modified is the config file. Then reload the config file 53 | if(event.src_path == get_config()): 54 | self.src,self.dest = reload_config() 55 | ## If the file modified is the db file. Then reload the patterns 56 | elif(event.src_path == get_db()): 57 | self.patterns = re.compile(get_patterns()) 58 | elif(event.src_path == get_config()+".bk"): 59 | None 60 | else : 61 | self.on_created(event) 62 | 63 | def runme(): 64 | src,dest = reload_config() 65 | home = expanduser("~") 66 | dirstocreate = Queue() 67 | filestocopy = Queue() 68 | filestodelete = Queue() 69 | handler = MyHandler(src,dest,dirstocreate,filestocopy,filestodelete) 70 | observer = Observer() 71 | observer.schedule(handler, path=src, recursive=True) 72 | observer.schedule(handler, path=home+"/.smartcopy/", recursive=True) 73 | observer.start() 74 | 75 | try: 76 | while True: 77 | time.sleep(1) 78 | 79 | while(not filestodelete.empty()): 80 | file = filestodelete.get() 81 | rfile = dest+file.replace(src,"") 82 | if(os.path.isfile(rfile)): 83 | subprocess.call(["rm",rfile]) 84 | if(os.path.isdir(rfile) and rfile.startswith(dest)): 85 | subprocess.call(["rm","-rf",rfile]) 86 | 87 | while(not dirstocreate.empty()): 88 | dir = dirstocreate.get() 89 | subprocess.call(["mkdir","-p",quote(dest+dir.replace(src,""))]) 90 | 91 | while(not filestocopy.empty()): 92 | file = filestocopy.get() 93 | subprocess.call(["cp",file,dest+file.replace(src,"")]) 94 | 95 | 96 | except KeyboardInterrupt: 97 | observer.stop() 98 | observer.join() 99 | 100 | if __name__ == "__main__": 101 | home = expanduser("~") 102 | smartdir = home+"/.smartcopy" 103 | 104 | try: 105 | pid = os.fork() 106 | if pid > 0: 107 | sys.exit(0) 108 | else : 109 | if(not os.path.exists(home+"/SmartCopy")): 110 | subprocess.call(["mkdir","-p",quote(home+"/SmartCopy")]) 111 | if(not os.path.exists(home+"/Dropbox")): 112 | subprocess.call(["mkdir","-p",quote(home+"/Dropbox")]) 113 | if(not os.path.exists(smartdir)): 114 | subprocess.call(["mkdir","-p",quote(smartdir)]) 115 | if(not os.path.exists(get_config())): 116 | fd = open(get_config(),"w") 117 | fd.write("source="+home+"/SmartCopy\n") 118 | fd.write("destination="+home+"/Dropbox\n") 119 | fd.close() 120 | if(not os.path.exists(get_db())): 121 | subprocess.call(["touch",get_db()]) 122 | 123 | except OSError, e: 124 | print >> sys.stderr, "fork failed: %d (%s)" % (e.errno, e.strerror) 125 | sys.exit(1) 126 | 127 | os.chdir("/") 128 | os.setsid() 129 | os.umask(0) 130 | f = open(os.devnull, 'w') 131 | sys.stdout = f 132 | sys.stderr = f 133 | runme() 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 71 | 72 | 83 | 84 | 85 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 133 | 134 | 137 | 138 | 141 | 142 | 143 | 144 | 147 | 148 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 182 | 183 | 197 | 198 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 1391052857293 222 | 1391052857293 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 252 | 253 | 264 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | --------------------------------------------------------------------------------