Skip to content

Commit 96a9512

Browse files
committed
initial commit
0 parents  commit 96a9512

16 files changed

+1737
-0
lines changed

.gitignore

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
MANIFEST
27+
28+
# PyInstaller
29+
# Usually these files are written by a python script from a template
30+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
31+
*.manifest
32+
*.spec
33+
34+
# Installer logs
35+
pip-log.txt
36+
pip-delete-this-directory.txt
37+
38+
# Unit test / coverage reports
39+
htmlcov/
40+
.tox/
41+
.coverage
42+
.coverage.*
43+
.cache
44+
nosetests.xml
45+
coverage.xml
46+
*.cover
47+
.hypothesis/
48+
.pytest_cache/
49+
50+
# Translations
51+
*.mo
52+
*.pot
53+
54+
# Django stuff:
55+
*.log
56+
.static_storage/
57+
.media/
58+
local_settings.py
59+
60+
# Flask stuff:
61+
instance/
62+
.webassets-cache
63+
64+
# Scrapy stuff:
65+
.scrapy
66+
67+
# Sphinx documentation
68+
docs/_build/
69+
70+
# PyBuilder
71+
target/
72+
73+
# Jupyter Notebook
74+
.ipynb_checkpoints
75+
76+
# pyenv
77+
.python-version
78+
79+
# celery beat schedule file
80+
celerybeat-schedule
81+
82+
# SageMath parsed files
83+
*.sage.py
84+
85+
# Environments
86+
.env
87+
.venv
88+
env/
89+
venv/
90+
ENV/
91+
env.bak/
92+
venv.bak/
93+
94+
# Spyder project settings
95+
.spyderproject
96+
.spyproject
97+
98+
# Rope project settings
99+
.ropeproject
100+
101+
# mkdocs documentation
102+
/site
103+
104+
# mypy
105+
.mypy_cache/

moonmen/client/Factory.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from client.Protocol import SynchronizationProtocol
2+
3+
from twisted.internet.protocol import ClientFactory
4+
5+
class SynchronisationFactory(ClientFactory):
6+
def __init__(self, fileSystemManager):
7+
self.fileSystemManager = fileSystemManager
8+
9+
def buildProtocol(self, addr):
10+
return SynchronizationProtocol(self)

moonmen/client/Protocol.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import json
2+
3+
from synchronization.SynchronizationManager import SynchronizationManager
4+
5+
from twisted.internet.error import ConnectionDone
6+
from twisted.internet.protocol import Protocol
7+
from twisted.internet import reactor
8+
9+
class SynchronizationProtocol(Protocol):
10+
def __init__(self, factory):
11+
self.factory = factory
12+
self.fileProcesses = []
13+
self.contents = {}
14+
15+
def connectionMade(self):
16+
modifications = self.factory.fileSystemManager.getDifferencesSinceLastSync();
17+
message = {
18+
'type' : 'modifications',
19+
'modifications' : modifications
20+
}
21+
self.transport.write(json.dumps(message).encode())
22+
23+
def connectionLost(self, reason):
24+
if reason.check(ConnectionDone):
25+
SynchronizationManager.synchronize(self.fileProcesses, self.contents, self.factory.fileSystemManager)
26+
self.factory.fileSystemManager.syncBackupState()
27+
reactor.stop()
28+
29+
def dataReceived(self, data):
30+
response = {
31+
'type' : 'error',
32+
'message' : 'unknown error'
33+
}
34+
try:
35+
message = json.loads(data.decode())
36+
if message['type'] == 'operations':
37+
response = self.handleModifications(message['operations'], message['files'])
38+
elif message['type'] == 'data':
39+
response = self.handleData(message['data'])
40+
else:
41+
response = {
42+
'type' : 'error',
43+
'messgae' : 'Message type not recognized'
44+
}
45+
except json.JSONDecodeError:
46+
response = {
47+
'type' : 'error',
48+
'message' : 'Message format not recognized'
49+
}
50+
finally:
51+
self.transport.write(json.dumps(response).encode())
52+
53+
def handleModifications(self, operations, files):
54+
self.fileProcesses = operations
55+
otherSystemRequiredData = self.factory.fileSystemManager.getFilesData(files)
56+
response = {
57+
'type' : 'data',
58+
'data' : otherSystemRequiredData
59+
}
60+
return response
61+
62+
def handleData(self, data): #needs-attention (maybe will need modifications)
63+
self.contents = data
64+
response = {
65+
'type' : 'end'
66+
}
67+
return response

moonmen/client/__init__.py

Whitespace-only changes.
+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
"""
2+
This module provides API to perform various operations on directory or file(s) based
3+
on the differences between the snapshots of the two directories.
4+
"""
5+
import os
6+
import shutil
7+
import pickle
8+
9+
from os.path import basename, normpath, relpath
10+
from itertools import starmap #needs-attention
11+
12+
from watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff
13+
14+
class FileSystemManager():
15+
16+
def __init__(self, directoryPath, backupDirectoryPath):
17+
self.directoryPath = directoryPath
18+
self.backupDirectoryPath = backupDirectoryPath
19+
self.listdir = lambda path: [
20+
p for p in os.listdir(path) if p != basename(normpath(backupDirectoryPath))] #needs-attention
21+
22+
def getDifferencesSinceLastSync(self):
23+
"""
24+
Get differences between the current directory and backup directory since last sync.
25+
"""
26+
oldSnapshot = self.getSnapshotOld()
27+
currentSnapshot = self.getSnapshotCurrent()
28+
difference = DirectorySnapshotDiff(oldSnapshot, currentSnapshot)
29+
return self.getDiffDict(difference)
30+
31+
def getSnapshotCurrent(self):
32+
"""
33+
Get the snapshot of the directory in current state.
34+
See http://pythonhosted.org/pydica-watchdog/_modules/watchdog/utils/dirsnapshot.html for more details
35+
"""
36+
return DirectorySnapshot(self.directoryPath, listdir=self.listdir)
37+
38+
def getSnapshotOld(self):
39+
"""
40+
Get the snapshot of backup directory.
41+
If backup directory is not present snapshot of current directory
42+
"""
43+
try:
44+
with open(''.join((self.backupDirectoryPath, '.snapshot')), 'rb') as snapshotFile:
45+
snapshot = pickle.load(snapshotFile)
46+
except FileNotFoundError:
47+
snapshot = DirectorySnapshot(
48+
self.directoryPath, listdir=lambda _: []) #needs-attention
49+
return snapshot
50+
51+
def getDiffDict(self, difference):
52+
"""
53+
Get the differences between two snapshots in the form of a dictionary
54+
55+
param difference: L{watchdog.utils.dirsnapshot.DirectorySnapshotDiff}
56+
"""
57+
diffDict = {}
58+
for key, value in difference.__dict__.items():
59+
if key in ('_dirs_moved', '_files_moved'):
60+
diffDict[key[1:]] = list(starmap(
61+
lambda p1, p2: (relpath(p1, self.directoryPath),
62+
relpath(p2, self.directoryPath)), value))
63+
else:
64+
diffDict[key[1:]] = list(map(lambda p: relpath(p, self.directoryPath), value))
65+
return diffDict
66+
67+
def syncBackupState(self):
68+
"""
69+
Make or sync the backup directory
70+
"""
71+
currentSnapshot = self.getSnapshotCurrent()
72+
try:
73+
self.deleteDirectory(self.backupDirectoryPath)
74+
except FileNotFoundError:
75+
pass
76+
self.copyDirectory(self.directoryPath, self.backupDirectoryPath)
77+
with open(''.join((self.backupDirectoryPath, '.snapshot')), 'wb') as snapshotFile:
78+
pickle.dump(currentSnapshot, snapshotFile)
79+
80+
def createDirectory(self, path):
81+
"""
82+
OS command for making directories
83+
84+
param path : path of the directory to create
85+
"""
86+
os.makedirs(relpath(path, self.directoryPath), exist_ok=True)
87+
88+
def deleteDirectory(self, path):
89+
"""
90+
Use shutil for deleting directory
91+
92+
param path : path of the directory to delete
93+
"""
94+
shutil.rmtree(relpath(path, self.directoryPath))
95+
96+
def moveDirectory(self, sourcePath, destinationPath):
97+
"""
98+
Use OS module for moving directory
99+
100+
param sourcePath: source path of the directory
101+
param destinationPath : destination path of the directory
102+
"""
103+
os.renames(relpath(sourcePath, self.directoryPath), relpath(destinationPath, self.directoryPath))
104+
105+
def copyDirectory(self, sourcePath, destinationPath):
106+
"""
107+
use shutil.copytree for copying the contents of the directory
108+
109+
param sourcePath: source path of the directory
110+
param destinationPath : destination path of the directory
111+
"""
112+
shutil.copytree(relpath(sourcePath, self.directoryPath), relpath(destinationPath, self.directoryPath),
113+
ignore=lambda *_: [basename(normpath(self.backupDirectoryPath))]) #needs-attention
114+
115+
def deleteFile(self, path):
116+
"""
117+
Use OS module to delete file.
118+
119+
param path : path of file to delete
120+
"""
121+
os.remove(relpath(path, self.directoryPath))
122+
123+
def moveFile(self, sourcePath, destinationPath):
124+
"""
125+
Use OS module to move file(s)
126+
127+
param sourcePath : source path of the file
128+
param destinationPath : destination path of the file
129+
"""
130+
os.renames(relpath(sourcePath, self.directoryPath), relpath(destinationPath, self.directoryPath))
131+
132+
def readFile(self, path):
133+
"""
134+
Read the contents of a file
135+
136+
paaram path : path of the file to read
137+
"""
138+
with open(relpath(self.directoryPath+'/'+path), 'r') as readFile:
139+
return readFile.readlines()
140+
141+
def writeFile(self, path, data):
142+
"""
143+
Write the given data to the file
144+
145+
param data : contents to be written to the file
146+
param path : path of the file to be written
147+
"""
148+
with open(relpath(self.directoryPath+'/'+path), 'w') as writeFile:
149+
writeFile.writelines(data)
150+
151+
def readBackupFile(self, path):
152+
"""
153+
Read the contents of the backup file
154+
155+
param path : path of the backup file
156+
"""
157+
try:
158+
return self.readFile(''.join((self.backupDirectoryPath, path)))
159+
except FileNotFoundError:
160+
return None
161+
162+
def getFilesData(self, files):
163+
"""
164+
Return the data of the file(s) provided in the form of a dictionary
165+
166+
param files : A python iterable containing the path of the files from which data should be read
167+
"""
168+
fileDataDictionary = {}
169+
for file in files:
170+
try:
171+
fileDataDictionary[file] = self.readFile(file)
172+
except FileNotFoundError:
173+
fileDataDictionary[file] = self.readBackupFile(file)
174+
return fileDataDictionary

moonmen/filesystem/__init__.py

Whitespace-only changes.

moonmen/server/Factory.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from server.Protocol import SynchronizationProtocol
2+
from twisted.internet.protocol import Factory
3+
4+
class SynchronisationFactory(Factory):
5+
6+
def __init__(self, fileSystemManager):
7+
self.fileSystemManager = fileSystemManager
8+
9+
def buildProtocol(self, addr):
10+
return SynchronizationProtocol(self)
11+
12+

0 commit comments

Comments
 (0)