Commit 44cbbee54dc5c5b714b914d91509f922cd9e7bed
0 parents
initial
Showing
68 changed files
with
7693 additions
and
0 deletions
Too many changes to show.
To preserve performance only 49 of 68 files are displayed.
README
0 → 100644
1 | +++ a/README | |
1 | +Instrukcja instalacji systemu zarządzania plikami. | |
2 | + | |
3 | + | |
4 | +W poniższej instrukcji zakładamy, że czytelnik posiada podstawową | |
5 | +wiedzę na temat administracji serwera svn. Zakładamy, że | |
6 | +po stronie serwera zainstalowane są następujące narzędzia: | |
7 | + | |
8 | +* subversion | |
9 | +* python (2.6 lub 2.7) | |
10 | +* pysvn | |
11 | + | |
12 | +GUI ułatwiające pobieranie i wysyłanie zaanotowanych plików | |
13 | +zbudowane jest na bazie: | |
14 | + | |
15 | +* python | |
16 | +* wxPython | |
17 | +* wxGlade | |
18 | + | |
19 | +Przygotowana została binarna wersja GUI pod system Windows, | |
20 | +dzięki czemu użytkownicy systemu nie muszą posiadać | |
21 | +zainstalowanych powyższych narzedzi. | |
22 | + | |
23 | + | |
24 | +------------------------------------------------------------------ | |
25 | + | |
26 | + | |
27 | +Razem z instrukcją README administrator systemu powinien otrzymać: | |
28 | + | |
29 | +* Moduł dfs -- implementację systemu zarządzania plikami, | |
30 | +* Katalog scripts -- zestaw skryptów ułatwiających tworzenie i | |
31 | + zarządzanie systemem, | |
32 | +* Moduł gui -- implementację GUI dla użytkowników systemu. | |
33 | +* Katalog z przykładową konfiguracją serwera | |
34 | + (pliki $SERVER.cfg oraz $USERS.cfg), | |
35 | +* Katalog z przykładową konfiguracją klienta GUI (plik $GUI.cfg). | |
36 | + | |
37 | +Ponadto, administrator powinien wygenerować klucze szyfrujące | |
38 | +połączenie klient-serwer -- pliki key.pem i cert.pem. | |
39 | +Odpowiednie informacje na temat generowania kluczy można znaleźć | |
40 | +pod adresem: | |
41 | + | |
42 | +http://docs.python.org/dev/library/ssl.html#certificates | |
43 | + | |
44 | + | |
45 | +================================================================== | |
46 | + | |
47 | + | |
48 | +1) Zakładanie nowego repozytorium. | |
49 | + | |
50 | + | |
51 | +Na serwerze, w katalogu, w którym ma znajdować się repozytorium, | |
52 | +należy wykonać komendę: | |
53 | + | |
54 | +$ svnadmin create $REPO.PATH | |
55 | + | |
56 | +W katalogu konfiguracyjnym repozytorium ($REPO.PATH/conf/) należy | |
57 | +zmodyfikować dwa pliki: | |
58 | + | |
59 | +i) svnserve.conf | |
60 | + | |
61 | +... | |
62 | +### and "none". The sample settings below are the defaults. | |
63 | +anon-access = none | |
64 | +auth-access = write | |
65 | +### The password-db option controls the location of the password | |
66 | +... | |
67 | +### Uncomment the line below to use the default password file. | |
68 | +password-db = passwd | |
69 | +### The authz-db option controls the location of the authorization | |
70 | +... | |
71 | + | |
72 | +ii) passwd | |
73 | + | |
74 | +W pliku passwd należy wpisać login i hasło, które wykorzystywane | |
75 | +będą po stronie serwera do zatwierdzania (commit-owania) zmian | |
76 | +w repozytorium, np.: | |
77 | + | |
78 | +... | |
79 | +machine = 95mnf83m-SDT%@vf | |
80 | +... | |
81 | + | |
82 | +Ten sam login i hasło należy podać w konfiguracji serwera systemu | |
83 | +zarządzania plikami, o czym w punkcie 2). W pliku passwd można | |
84 | +podać również hasła dla innych użytkowników repozytorium -- więcej | |
85 | +na ten temat można znaleźć w dokumentacji subversion. | |
86 | + | |
87 | +Hasła użytkowników systemu (anotatorów) również uzupełniane są w | |
88 | +konfiguracji systemu zarządzania plikami opisanej w 2). | |
89 | + | |
90 | + | |
91 | +------------------------------------------------------------------ | |
92 | + | |
93 | + | |
94 | +Na serwerze, w innym katalogu niż zostało założone repozytorium, | |
95 | +należy utworzyć kopię roboczą repozytorium, z której korzystać | |
96 | +będzie system zarządzania plikami. W tym celu należy wpisać: | |
97 | + | |
98 | +$ svn co file://$REPO.PATH $COPY.PATH | |
99 | +Pobrano wersję 0. | |
100 | + | |
101 | +Gdzie $REPO.PATH to ścieżka repozytorium, a $COPY.PATH to | |
102 | +docelowy katalog kopii roboczej. | |
103 | + | |
104 | + | |
105 | +UWAGA: Zamiast ścieżki postaci file://$REPO.PATH, można również | |
106 | +podać adres w postaci svn://$REPO.ADDR -- adres, który jest | |
107 | +dostępny dla użytkowników repozytorium pracujących poza | |
108 | +serwerem. Ponadto, kopię roboczą można założyć na innym | |
109 | +serwerze niż repozytorium. Kopia robocza musi jednak znajdować | |
110 | +się na tej samej maszynie, na której będzie później uruchamiany | |
111 | +serwer systemu zarządzania plikami. Wadą tego rozwiązania | |
112 | +może być wolniejsze działanie systemu. | |
113 | + | |
114 | + | |
115 | +Pustą kopię roboczą należy na wstępie wypełnić strukturą katalogów | |
116 | +dla systemu zarządzania plikami. Służy do tego skrypt | |
117 | +fill_empty_repo.sh znajdujący się w katalogu scripts dostarczonym | |
118 | +razem z tą instrukcją. Skrypt wywołuje się w następujący sposób: | |
119 | + | |
120 | +$ ./fill_empty_repo.sh $COPY.PATH | |
121 | + | |
122 | +Struktura katalogów powinna wyglądać następująco: | |
123 | + | |
124 | +$COPY.PATH/ | |
125 | + new <- katalog z nowymi plikami | |
126 | + annotation/ <- wyniki anotacji plików | |
127 | + A | |
128 | + B | |
129 | + adjudication <- wyniki super-anotacji plików | |
130 | + db.xml <- baza danych z bierzącymi informacjami | |
131 | + dotyczącymi anotacji | |
132 | + | |
133 | +Na koniec należy watwierdzić zmiany wykonane w kopii roboczej: | |
134 | + | |
135 | +$ svn add $COPY.PATH/* | |
136 | +$ svn commit --username=machine --password=95mnf83m-SDT%@vf -m "struktura" | |
137 | + | |
138 | + | |
139 | +UWAGA: W katalogach annotation/A, annotation/B oraz adjudication | |
140 | +znajdują się wysłane przez anotatorów na serwer pliki. Nie oznacza | |
141 | +to jednak, że anotacja tych plików przez ich posiadaczy zakończyła | |
142 | +się. Taką informację można uzyskać na podstawie bazy danych | |
143 | +(pliki, których anotacja/super-anotacja zakończyła się, będą | |
144 | +uzupełnione o element <checkinDate> w bazie danych). | |
145 | + | |
146 | + | |
147 | +================================================================== | |
148 | + | |
149 | + | |
150 | +2) Konfiguracja systemu zarządzania plikami. | |
151 | + | |
152 | + | |
153 | +Konfiguracja systemu odbywa się w trzech plikach: | |
154 | +* $SERVER.cfg | |
155 | +* $USERS.cfg | |
156 | +* $PASSWD | |
157 | + | |
158 | +Razem z instrukcją i implementacją serwera powinny zostać | |
159 | +dostarczone również przykładowe pliki konfiguracyjne, w których | |
160 | +można znaleźć dokładniejsze informacje na temat konfiguracji | |
161 | +serwera. | |
162 | + | |
163 | +i) $SERVER.cfg | |
164 | + | |
165 | +Plik $SERVER.cfg jest głównym plikiem konfiguracyjnym i jedynym | |
166 | +wczytywanym bezpośrednio przez serwer systemu (znajdują się | |
167 | +w nim również ścieżki do pozostałych dwóch plików konfiguracyjnych | |
168 | +-- $USERS.cfg i $PASSWD). | |
169 | + | |
170 | +ii) $USERS.cfg | |
171 | + | |
172 | +Plik zawiera informacje o uprawnieniach do super-anotacji oraz | |
173 | +o limitach pobierania plików. | |
174 | + | |
175 | +ii) $PASSWD | |
176 | + | |
177 | +W pliku znajdują się hasła określone dla użytkowników systemu | |
178 | +(anotatorów). Poszczególne linie tego pliku mają postać: | |
179 | +login = hasło | |
180 | + | |
181 | + | |
182 | +================================================================== | |
183 | + | |
184 | + | |
185 | +3) Używanie serwera systemu zarzadznania plikami. | |
186 | + | |
187 | + | |
188 | +Do uruchamiania serwera służy narzędzie server.py w module dfs. | |
189 | +Serwer uruchamia się następującą komendą: | |
190 | + | |
191 | +$ python server.py $SERVER.cfg | |
192 | + | |
193 | +Ewentualne błędy będą wypisywane na standardowe wyjście. | |
194 | + | |
195 | + | |
196 | +------------------------------------------------------------------ | |
197 | + | |
198 | + | |
199 | +Karmienie systemu nowymi plikami przeznaczonymi do anotacji. | |
200 | + | |
201 | +Do tego celu służy skrypt add_files.py w katalogu scripts. | |
202 | + | |
203 | +$ python add_files.py $SERVER.cfg PATH1 PATH2 ... | |
204 | + | |
205 | +gdzie PATH1, PATH2, ... to ścieżki do plików, które mają zostać | |
206 | +dodane do repozytorium. Zmiany nie są automatycznie commit-owane, | |
207 | +więc trzeba użyć komendy svn commit do ich zatwierdzenia: | |
208 | + | |
209 | +UWAGA: Dodawanie plików, jak również jakiekolwiek prace | |
210 | +administracyje w repozytorium systemu powinny być przeprowadzane | |
211 | +przy wyłączonym serwerze server.py. Serwer można w sposób | |
212 | +bezpieczny wyłączyć wysyłając sygnał SIGINT (np. Control-C) | |
213 | +(Sygnał zostanie zignorowany, jeśli odbywa się komunikacja | |
214 | +z serwerem). | |
215 | + | |
216 | + | |
217 | +================================================================== | |
218 | + | |
219 | + | |
220 | +4) Graficzny interfejs systemu zarządzania plikami. | |
221 | + | |
222 | + | |
223 | +Implementacja GUI znajduje się w module gui dostarczonym | |
224 | +razem z instrukcją. GUI przeznaczone jest dla użytkowników | |
225 | +systemu, czyli anotatorów. | |
226 | + | |
227 | + | |
228 | +------------------------------------------------------------------ | |
229 | + | |
230 | + | |
231 | +Konfiguracja GUI znajduje się w katalogu gui/data w pliku | |
232 | +global.cfg. W tym samym katalogu powinien znajdować się | |
233 | +wygenerowany przez administratora klucz publiczny (cert.pem). | |
234 | +Dokładniejsze informacje na temat konfiguracji znajdują się w | |
235 | +przykładowym pliku konfiguracyjnym gui/data/global.cfg. | |
236 | + | |
237 | + | |
238 | +------------------------------------------------------------------ | |
239 | + | |
240 | + | |
241 | +GUI uruchamia się komendą: | |
242 | + | |
243 | +$ python manager.py | |
... | ... |
dfs/__init__.py
0 → 100644
1 | +++ a/dfs/__init__.py | |
... | ... |
dfs/config.py
0 → 100644
1 | +++ a/dfs/config.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import re | |
3 | +import os | |
4 | + | |
5 | +SECTION = re.compile("^\[(.*)\]$") | |
6 | +SUBSECTION = re.compile("^\{(.*)\}$") | |
7 | +ENTRY = re.compile("^([^=\s]+)\s*=\s*([^=]+)\s*$") | |
8 | + | |
9 | +def cut_comment(s): | |
10 | + return s.split("#")[0] | |
11 | + | |
12 | +class Config: | |
13 | + | |
14 | + def __init__(self, path): | |
15 | + section = None | |
16 | + subsection = None | |
17 | + self.d = {} | |
18 | + with open(path) as f: | |
19 | + for line in f: | |
20 | + line = cut_comment(line).strip() | |
21 | + | |
22 | + m = SECTION.search(line) | |
23 | + if m is not None: | |
24 | + section = m.group(1) | |
25 | + subsection = None | |
26 | + continue | |
27 | + | |
28 | + m = SUBSECTION.search(line) | |
29 | + if m is not None: | |
30 | + subsection = m.group(1) | |
31 | + continue | |
32 | + | |
33 | + m = ENTRY.search(line) | |
34 | + if m is not None: | |
35 | + key = m.group(1) | |
36 | + val = m.group(2) | |
37 | + if subsection == "paths": | |
38 | + # print path, val, os.path.join(path, val) | |
39 | + val = self.relative_path(val, path) | |
40 | + if section is not None: | |
41 | + self.d[section + "." + key] = val | |
42 | + else: | |
43 | + self.d[key] = val | |
44 | + | |
45 | + def relative_path(self, path, ref): | |
46 | + return os.path.join(os.path.dirname(ref), path) | |
47 | + | |
48 | + def __getitem__(self, key): | |
49 | + return self.d[key] | |
... | ... |
dfs/database.py
0 → 100644
1 | +++ a/dfs/database.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import time | |
3 | +from datetime import date, datetime | |
4 | +from collections import defaultdict | |
5 | + | |
6 | +from xml.etree.ElementTree import Element, ElementTree, tostring | |
7 | +import xml.etree.ElementTree as ET | |
8 | + | |
9 | +class Database: | |
10 | + | |
11 | + def __init__(self, db, anno_per_file, from_file=True): | |
12 | + if from_file: | |
13 | + self.db_name = db | |
14 | + self.tree = ET.parse(db) | |
15 | + self.root = self.tree.getroot() | |
16 | + else: | |
17 | + self.db_name = None | |
18 | + self.tree = None | |
19 | + self.root = ET.fromstring(db) | |
20 | + | |
21 | + self.anno_per_file = anno_per_file | |
22 | + self.make_index() | |
23 | + | |
24 | + def owned(self, ann_elem): | |
25 | + return not self.finished(ann_elem) and not self.returned(ann_elem) | |
26 | + | |
27 | + def finished(self, ann_elem): | |
28 | + return ann_elem.find("checkinDate") is not None | |
29 | + | |
30 | + def returned(self, ann_elem): | |
31 | + return ann_elem.find("return") is not None | |
32 | + | |
33 | + def get_reason(self, ann_elem): | |
34 | + reas = ann_elem.find("return").find("reason").text | |
35 | + if reas is None: | |
36 | + reas = "" | |
37 | + return reas | |
38 | + | |
39 | + def rejected(self, file_elem): | |
40 | + return file_elem.find("rejected") is not None | |
41 | + | |
42 | + def fixed(self, ann_elem): | |
43 | + ret = ann_elem.find("return") | |
44 | + if ret is not None and ret.find("fixed") is not None: | |
45 | + return True | |
46 | + return False | |
47 | + | |
48 | + def ann_elem_for_user(self, file_elem, user): | |
49 | + idx = 0 | |
50 | + for ann_elem in file_elem.findall("ann"): | |
51 | + if ann_elem.find("annName").text == user: | |
52 | + return ann_elem, idx | |
53 | + idx = idx + 1 | |
54 | + return None, None | |
55 | + | |
56 | + def sann_elem_for_user(self, file_elem, user): | |
57 | + for ann_elem in file_elem.findall("s_ann"): | |
58 | + if ann_elem.find("annName").text == user: | |
59 | + return ann_elem | |
60 | + return None | |
61 | + | |
62 | + def fixed_for_user(self, file_elem, user, is_adj): | |
63 | + if is_adj: | |
64 | + ann = self.sann_elem_for_user(file_elem, user) | |
65 | + else: | |
66 | + ann, idx = self.ann_elem_for_user(file_elem, user) | |
67 | + if ann is not None: | |
68 | + return self.fixed(ann) | |
69 | + return False | |
70 | + | |
71 | + def make_index(self): | |
72 | + self.file_index = {} | |
73 | + self.ann_owned_index = defaultdict(list) | |
74 | + self.adj_owned_index = defaultdict(list) | |
75 | + | |
76 | + for file_elem in self.root: | |
77 | + name_elem = file_elem.find("name") | |
78 | + if name_elem == None: | |
79 | + raise Exception("No name assigned to a file element !") | |
80 | + self.file_index[name_elem.text] = file_elem | |
81 | + | |
82 | + if self.rejected(file_elem): | |
83 | + continue | |
84 | + | |
85 | + for ann_elem in file_elem.findall("ann"): | |
86 | + if self.owned(ann_elem): | |
87 | + ann_name = ann_elem.find("annName").text | |
88 | + self.ann_owned_index[ann_name].append(file_elem) | |
89 | + | |
90 | + for ann_elem in file_elem.findall("s_ann"): | |
91 | + if self.owned(ann_elem): | |
92 | + ann_name = ann_elem.find("annName").text | |
93 | + self.adj_owned_index[ann_name].append(file_elem) | |
94 | + | |
95 | + def fix(self, ann_elem): | |
96 | + if not self.returned(ann_elem): | |
97 | + raise Exception("File not returned in database!") | |
98 | + if self.fixed(ann_elem): | |
99 | + raise Exception("File already fixed in database!") | |
100 | + | |
101 | + ret = ann_elem.find("return") | |
102 | + fixed = ET.SubElement(ret, "fixed") | |
103 | + | |
104 | + def add(self, file_name): | |
105 | + if self.file_index.has_key(file_name): | |
106 | + raise Exception("File name already in database !") | |
107 | + file_elem = ET.SubElement(self.root, "file") | |
108 | + name_elem = ET.SubElement(file_elem, "name") | |
109 | + name_elem.text = file_name | |
110 | + date_elem = ET.SubElement(file_elem, "addDate") | |
111 | + date_elem.text = time.strftime("%c") | |
112 | + self.file_index[file_name] = file_elem | |
113 | + | |
114 | + def remove(self, file_name): | |
115 | + if not self.file_index.has_key(file_name): | |
116 | + raise Exception("File name not in database !") | |
117 | + file_elem = self.file_index[file_name] | |
118 | + self.root.remove(file_elem) | |
119 | + self.make_index() | |
120 | + | |
121 | + def reject(self, file_name, reason): | |
122 | + if not self.file_index.has_key(file_name): | |
123 | + raise Exception("File name not in database !") | |
124 | + file_elem = self.file_index[file_name] | |
125 | + if self.rejected(file_elem): | |
126 | + raise Exception("File already rejected !") | |
127 | + rej_elem = ET.SubElement(file_elem, "rejected") | |
128 | + rej_elem.text = reason | |
129 | + self.make_index() | |
130 | + | |
131 | + # For prettyprint, used in 'save' method. | |
132 | + def indent(self, elem, level=0): | |
133 | + i = "\n" + level * " " | |
134 | + if len(elem): | |
135 | + if not elem.text or not elem.text.strip(): | |
136 | + elem.text = i + " " | |
137 | + if not elem.tail or not elem.tail.strip(): | |
138 | + elem.tail = i | |
139 | + for elem in elem: | |
140 | + self.indent(elem, level + 1) | |
141 | + if not elem.tail or not elem.tail.strip(): | |
142 | + elem.tail = i | |
143 | + else: | |
144 | + if level and (not elem.tail or not elem.tail.strip()): | |
145 | + elem.tail = i | |
146 | + | |
147 | + def save(self): | |
148 | + if not self.db_name: | |
149 | + raise Exception("XML not read from file !") | |
150 | + | |
151 | + self.indent(self.root) | |
152 | + self.tree.write(self.db_name, encoding="utf-8") | |
153 | + | |
154 | + def upload_prevention(self, file_name, user, is_super): | |
155 | + if not self.file_index.has_key(file_name): | |
156 | + return "Filename " + file_name.encode('utf-8') + " not known by server." | |
157 | + file_elem = self.file_index[file_name] | |
158 | + if self.rejected(file_elem): | |
159 | + return "File rejected: " + file_name.encode('utf-8') | |
160 | + | |
161 | + if is_super: | |
162 | + ann_elem = file_elem.find("s_ann") | |
163 | + if ann_elem is None: | |
164 | + return "No superannotator assigned to file " + file_name.encode('utf-8') | |
165 | + | |
166 | + if user != ann_elem.find("annName").text: | |
167 | + return "Different superannotator assigned to file " + file_name.encode('utf-8') | |
168 | + | |
169 | + if ann_elem.find("checkinDate") is not None: | |
170 | + return "File " + file_name.encode('utf-8') + " already checked in!" | |
171 | + | |
172 | + return None | |
173 | + | |
174 | + else: | |
175 | + annotations = file_elem.findall("ann") | |
176 | + owners = map(lambda ann: ann.find("annName").text, annotations) | |
177 | + if not user in owners: | |
178 | + return "User " + user + " doesn't own the file " + file_name.encode('utf-8') | |
179 | + | |
180 | + i = owners.index(user) | |
181 | + ann_elem = annotations[i] | |
182 | + if ann_elem.find("checkinDate") is not None: | |
183 | + return "User " + user + " already checked in the file " + file_name.encode('utf-8') | |
184 | + | |
185 | + return None | |
186 | + | |
187 | + def upload_dest(self, file_name, user): | |
188 | + """Upload destination (annName element and ID -- 1 or 2) directory.""" | |
189 | + file_elem = self.file_index[file_name] | |
190 | + annotations = file_elem.findall("ann") | |
191 | + | |
192 | + owners = map(lambda ann: ann.find("annName").text, annotations) | |
193 | + exc = Exception("Cannot upload " + file_name.encode('utf-8') | |
194 | + + " file by " + user + " annotator !") | |
195 | + if not user in owners: | |
196 | + raise exc | |
197 | + | |
198 | + i = owners.index(user) | |
199 | + ann_elem = annotations[i] | |
200 | + if ann_elem.find("checkinDate") is not None: | |
201 | + raise exc | |
202 | + | |
203 | + return (ann_elem, i) | |
204 | + | |
205 | + def upload_id(self, file_name, user): | |
206 | + return self.upload_dest(file_name, user)[1] | |
207 | + | |
208 | + def return_file(self, file_name, user, reason): | |
209 | + (ann_elem, i) = self.upload_dest(file_name, user) | |
210 | + ret_elem = ET.SubElement(ann_elem, "return") | |
211 | + date_elem = ET.SubElement(ret_elem, "date") | |
212 | + date_elem.text = time.strftime("%c") | |
213 | + date_elem = ET.SubElement(ret_elem, "reason") | |
214 | + if reason is None: | |
215 | + reason = "" | |
216 | + date_elem.text = reason | |
217 | + return i | |
218 | + | |
219 | + def return_file_prim(self, file_name, user, reason): | |
220 | + file_elem = self.file_index[file_name] | |
221 | + ann_elem = file_elem.find("s_ann") | |
222 | + | |
223 | + if (user != ann_elem.find("annName").text or | |
224 | + ann_elem.find("checkinDate") is not None): | |
225 | + raise Exception("Cannot upload " + file_name.encode('utf-8') | |
226 | + + " file by " + user + " annotator !") | |
227 | + | |
228 | + ret_elem = ET.SubElement(ann_elem, "return") | |
229 | + date_elem = ET.SubElement(ret_elem, "date") | |
230 | + date_elem.text = time.strftime("%c") | |
231 | + date_elem = ET.SubElement(ret_elem, "reason") | |
232 | + if reason is None: | |
233 | + reason = "" | |
234 | + date_elem.text = reason | |
235 | + | |
236 | + def upload(self, file_name, user): | |
237 | + (ann_elem, i) = self.upload_dest(file_name, user) | |
238 | + date_elem = ET.SubElement(ann_elem, "checkinDate") | |
239 | + date_elem.text = time.strftime("%c") | |
240 | + return i | |
241 | + | |
242 | + def upload_prim(self, file_name, user): | |
243 | + file_elem = self.file_index[file_name] | |
244 | + ann_elem = file_elem.find("s_ann") | |
245 | + | |
246 | + if (user != ann_elem.find("annName").text or self.finished(ann_elem)): | |
247 | + raise Exception("Cannot upload " + file_name.encode('utf-8') | |
248 | + + " file by " + user + " annotator !") | |
249 | + | |
250 | + date_elem = ET.SubElement(ann_elem, "checkinDate") | |
251 | + date_elem.text = time.strftime("%c") | |
252 | + | |
253 | + def download(self, file_name, user): | |
254 | + file_elem = self.file_index[file_name] | |
255 | + | |
256 | + annotations = file_elem.findall("ann") | |
257 | + owners = map(lambda ann: ann.find("annName").text, annotations) | |
258 | + | |
259 | + if self.fixed_for_user(file_elem, user, False): | |
260 | + ann, idx = self.ann_elem_for_user(file_elem, user) | |
261 | + date = ann.find("return").find("date").text | |
262 | + ann.remove(ann.find("return")) | |
263 | + | |
264 | + date_elem = ET.SubElement(ann, "returnDate") | |
265 | + date_elem.text = date | |
266 | + | |
267 | + date_elem = ET.SubElement(ann, "checkoutDate") | |
268 | + date_elem.text = time.strftime("%c") | |
269 | + | |
270 | + return idx | |
271 | + | |
272 | + else: | |
273 | + if user in owners or len(owners) + 1 > self.anno_per_file: | |
274 | + raise Exception("Cannot set " + user + " annotator to '" | |
275 | + + file_name.encode('utf-8') + "' file !") | |
276 | + | |
277 | + ann_elem = ET.SubElement(file_elem, "ann") | |
278 | + ann_name_elem = ET.SubElement(ann_elem, "annName") | |
279 | + ann_name_elem.text = user | |
280 | + | |
281 | + date_elem = ET.SubElement(ann_elem, "checkoutDate") | |
282 | + date_elem.text = time.strftime("%c") | |
283 | + | |
284 | + return None | |
285 | + | |
286 | + def download_prim(self, file_name, user): | |
287 | + file_elem = self.file_index[file_name] | |
288 | + | |
289 | + if self.fixed_for_user(file_elem, user, True): | |
290 | + sann = self.sann_elem_for_user(file_elem, user) | |
291 | + date = sann.find("return").find("date").text | |
292 | + sann.remove(sann.find("return")) | |
293 | + | |
294 | + date_elem = ET.SubElement(sann, "returnDate") | |
295 | + date_elem.text = date | |
296 | + | |
297 | + date_elem = ET.SubElement(sann, "checkoutDate") | |
298 | + date_elem.text = time.strftime("%c") | |
299 | + | |
300 | + return True | |
301 | + | |
302 | + else: | |
303 | + if len(file_elem.findall("s_ann")) > 0: | |
304 | + raise Exception("Cannot set " + user + " adjudicator to '" | |
305 | + + file_name.encode('utf-8') + "' file !") | |
306 | + | |
307 | + ann_elem = ET.SubElement(file_elem, "s_ann") | |
308 | + ann_name_elem = ET.SubElement(ann_elem, "annName") | |
309 | + ann_name_elem.text = user | |
310 | + | |
311 | + date_elem = ET.SubElement(ann_elem, "checkoutDate") | |
312 | + date_elem.text = time.strftime("%c") | |
313 | + | |
314 | + return False | |
315 | + | |
316 | + def for_annotation(self, user): | |
317 | + priority = [] | |
318 | + result = [] | |
319 | + for file_elem in self.root: | |
320 | + if self.rejected(file_elem) or file_elem.find("s_ann") is not None: | |
321 | + continue | |
322 | + | |
323 | + anns = file_elem.findall("ann") | |
324 | + owners = map(lambda ann: ann.find("annName").text, anns) | |
325 | + if len(owners) < self.anno_per_file and user not in owners: | |
326 | + result.append(file_elem.find("name").text) | |
327 | + | |
328 | + if self.fixed_for_user(file_elem, user, False): | |
329 | + priority.append(file_elem.find("name").text) | |
330 | + | |
331 | + return priority, result | |
332 | + | |
333 | + def for_adjudication(self, user): | |
334 | + priority = [] | |
335 | + result = [] | |
336 | + for file_elem in self.root: | |
337 | + if self.rejected(file_elem): | |
338 | + continue | |
339 | + | |
340 | + anns = file_elem.findall("ann") | |
341 | + owners = map(lambda ann: ann.find("annName").text, anns) | |
342 | + checked = map(lambda ann: ann.find("checkinDate") != None, anns) | |
343 | + if (len(owners) == self.anno_per_file | |
344 | + and file_elem.find("s_ann") is None | |
345 | + and user not in owners | |
346 | + and all(checked)): | |
347 | + result.append(file_elem.find("name").text) | |
348 | + | |
349 | + if self.fixed_for_user(file_elem, user, True): | |
350 | + priority.append(file_elem.find("name").text) | |
351 | + | |
352 | + return priority, result | |
353 | + | |
354 | + def owns_normal(self, user): | |
355 | + return [ file_elem.find("name").text | |
356 | + for file_elem | |
357 | + in self.ann_owned_index[user] ] | |
358 | + | |
359 | + def owns_super(self, user): | |
360 | + return [ file_elem.find("name").text | |
361 | + for file_elem | |
362 | + in self.adj_owned_index[user] ] | |
363 | + | |
364 | + def owns(self, user): | |
365 | + return self.owns_normal(user) + self.owns_super(user) | |
366 | + | |
367 | + def finished_count(self, user): | |
368 | + n = 0 | |
369 | + for file_elem in self.root: | |
370 | + | |
371 | + if self.rejected(file_elem): | |
372 | + continue | |
373 | + | |
374 | + items = file_elem.findall("ann") | |
375 | + items.extend(file_elem.findall("s_ann")) | |
376 | + for ann_elem in items: | |
377 | + ann_name = ann_elem.find("annName").text | |
378 | + if ann_name == user and self.finished(ann_elem) and not self.returned(ann_elem): | |
379 | + n += 1 | |
380 | + | |
381 | + return n | |
... | ... |
dfs/msg/__init__.py
0 → 100644
1 | +++ a/dfs/msg/__init__.py | |
... | ... |
dfs/msg/message.py
0 → 100644
1 | +++ a/dfs/msg/message.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +__all__ = ["decode", "Message", "OkMessage", "KoMessage", "DownloadRequest" | |
3 | + , "UploadRequest", "DownloadPrimRequest", "ClientDone", "ServerDone" | |
4 | + , "NumMessage", "BoolMessage", "ReturnRequest", "CheckoutRequest", | |
5 | + "CheckinRequest", "StatsRequest"] | |
6 | + | |
7 | + | |
8 | +class Message(object): | |
9 | + | |
10 | + """ | |
11 | + Base class for SSL communication messages. | |
12 | + """ | |
13 | + | |
14 | + def __init__(self, contents=""): | |
15 | + self.contents = contents | |
16 | + | |
17 | + def get_contents(self): | |
18 | + return self.contents | |
19 | + | |
20 | + @classmethod | |
21 | + def from_contents(cls, contents): | |
22 | + return cls(contents) | |
23 | + | |
24 | + def encode(self): | |
25 | + """ | |
26 | + Prepare message to be sent over network. | |
27 | + WARNING: Do not override this method. | |
28 | + """ | |
29 | + # Add "-" on the beggining to prevent | |
30 | + # message contents from being void. | |
31 | + return self.msg_type, "-" + self.get_contents() | |
32 | + | |
33 | + | |
34 | +class NumMessage(Message): | |
35 | + | |
36 | + def __init__(self, n): | |
37 | + self.number = n | |
38 | + | |
39 | + def get_contents(self): | |
40 | + return str(self.number) | |
41 | + | |
42 | + @classmethod | |
43 | + def from_contents(cls, contents): | |
44 | + return cls(int(contents)) | |
45 | + | |
46 | + def get_number(self): | |
47 | + return self.number | |
48 | + | |
49 | + | |
50 | +class BoolMessage(Message): | |
51 | + | |
52 | + def __init__(self, b): | |
53 | + self.b = b | |
54 | + | |
55 | + def get_contents(self): | |
56 | + return "1" if self.b else "0" | |
57 | + | |
58 | + @classmethod | |
59 | + def from_contents(cls, contents): | |
60 | + return cls(bool(int(contents))) | |
61 | + | |
62 | + def get_boolean(self): | |
63 | + return self.b | |
64 | + | |
65 | +class ReturnRequest(NumMessage): | |
66 | + pass | |
67 | + | |
68 | +class CheckoutRequest(Message): | |
69 | + pass | |
70 | + | |
71 | +class CheckinRequest(NumMessage): | |
72 | + pass | |
73 | + | |
74 | +class DownloadRequest(NumMessage): | |
75 | + pass | |
76 | + | |
77 | +class DownloadPrimRequest(NumMessage): | |
78 | + pass | |
79 | + | |
80 | +class UploadRequest(NumMessage): | |
81 | + pass | |
82 | + | |
83 | +class OkMessage(Message): | |
84 | + pass | |
85 | + | |
86 | +class KoMessage(Message): | |
87 | + pass | |
88 | + | |
89 | +class StatsRequest(Message): | |
90 | + pass | |
91 | + | |
92 | +class ServerDone(Message): | |
93 | + pass | |
94 | + | |
95 | +class ClientDone(Message): | |
96 | + pass | |
97 | + | |
98 | + | |
99 | +def subclasses(cls): | |
100 | + yield cls | |
101 | + for child in cls.__subclasses__(): | |
102 | + for desc in subclasses(child): | |
103 | + yield desc | |
104 | + | |
105 | +def decode(msg_type, contents): | |
106 | + for cls in subclasses(Message): | |
107 | + if cls.msg_type == msg_type: | |
108 | + # Discard first, dummy character; | |
109 | + # Confront Message.encode method. | |
110 | + return cls.from_contents(contents[1:]) | |
111 | + | |
112 | +for i, cls in enumerate(subclasses(Message)): | |
113 | + cls.msg_type = i | |
... | ... |
dfs/msg/stream.py
0 → 100644
1 | +++ a/dfs/msg/stream.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import struct | |
3 | + | |
4 | +from message import decode | |
5 | + | |
6 | +__all__ = ["read_msg", "write_msg", "EndOfStream"] | |
7 | + | |
8 | +class EndOfStream(Exception): | |
9 | + pass | |
10 | + | |
11 | +class BadMessage(Exception): | |
12 | + pass | |
13 | + | |
14 | +def read_msg(connstream): | |
15 | + encoded = read_encoded(connstream) | |
16 | + return decode(*encoded) | |
17 | + | |
18 | +def accept_msg(connstream, cls): | |
19 | + msg = read_msg(connstream) | |
20 | + if type(msg) != cls: | |
21 | + raise BadMessage("Expected %s, got %s" % | |
22 | + (cls.__name__, type(msg).__name__)) | |
23 | + return msg | |
24 | + | |
25 | +def write_msg(connstream, msg): | |
26 | + encoded = msg.encode() | |
27 | + write_encoded(connstream, *encoded) | |
28 | + | |
29 | +def read_encoded(connstream): | |
30 | + _type = read_type(connstream) | |
31 | + length = read_length(connstream) | |
32 | + content = read_content(connstream, length) | |
33 | + return _type, content | |
34 | + | |
35 | +def write_encoded(connstream, _type, content): | |
36 | + write_type(connstream, _type) | |
37 | + write_length(connstream, len(content)) | |
38 | + write_content(connstream, content) | |
39 | + | |
40 | +def read_type(connstream): | |
41 | + data = read_all(connstream, 1) | |
42 | + return struct.unpack("!B", data)[0] | |
43 | + | |
44 | +def write_type(connstream, _type): | |
45 | + data = struct.pack("!B", _type) | |
46 | + write_all(connstream, data) | |
47 | + | |
48 | +def read_length(connstream): | |
49 | + data = read_all(connstream, 4) | |
50 | + return struct.unpack("!L", data)[0] | |
51 | + | |
52 | +def write_length(connstream, n): | |
53 | + data = struct.pack("!L", n) | |
54 | + write_all(connstream, data) | |
55 | + | |
56 | +def read_content(connstream, length): | |
57 | + return read_all(connstream, length) | |
58 | + | |
59 | +def write_content(connstream, content): | |
60 | + write_all(connstream, content) | |
61 | + | |
62 | +def write_all(connstream, data): | |
63 | + k = connstream.write(data) | |
64 | + if k != len(data): | |
65 | + raise Exception("Did not send all data !") | |
66 | + | |
67 | +def read_all(connstream, k): | |
68 | + data = "" | |
69 | + while k > 0: | |
70 | + # part = connstream.read() | |
71 | + part = connstream.read(min(k, 1024)) | |
72 | + if len(part) == 0: | |
73 | + raise EndOfStream | |
74 | + k -= len(part) | |
75 | + # if k < 0: | |
76 | + # raise Exception("Received more than expected !") | |
77 | + data += part | |
78 | + return data | |
79 | + | |
... | ... |
dfs/repo.py
0 → 100644
1 | +++ a/dfs/repo.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import os | |
3 | +import shutil | |
4 | +import pysvn | |
5 | + | |
6 | +class Repo: | |
7 | + | |
8 | + def __init__(self, path, login, passwd): | |
9 | + self.path = path | |
10 | + def get_login(_realm, _username, _may_save): | |
11 | + return True, login, passwd, False | |
12 | + self.svn = pysvn.Client() | |
13 | + self.svn.callback_get_login = get_login | |
14 | + self.svn.update(self.path) | |
15 | + | |
16 | + def add(self, src_path): | |
17 | + part_name = os.path.basename(src_path) | |
18 | + dest_path = os.path.join(self.new_path(), part_name) | |
19 | + | |
20 | + if (not self.svn.info(dest_path) is None) or (os.path.exists(dest_path)): | |
21 | + raise Exception(src_path+" already present in repository.") | |
22 | + | |
23 | + shutil.copy(src_path, dest_path) | |
24 | + self.svn.add(dest_path) | |
25 | + | |
26 | + def remove(self, filename, anno_per_file): | |
27 | + paths = set() | |
28 | + paths.add(os.path.join(self.new_path(), filename)) | |
29 | + for i in range(anno_per_file): | |
30 | + paths.add(self.upload_path(filename, i)) | |
31 | + paths.add(os.path.join(self.finito_path(), filename)) | |
32 | + | |
33 | + removed = [] | |
34 | + for path in paths: | |
35 | + if os.path.exists(path): | |
36 | + self.svn.remove(path) | |
37 | + removed.append(path) | |
38 | + | |
39 | + return removed | |
40 | + | |
41 | + def db_path(self): | |
42 | + return os.path.join(self.path, "db.xml") | |
43 | + | |
44 | + def new_path(self): | |
45 | + return os.path.join(self.path, "new") | |
46 | + | |
47 | + def ann_path(self, idx): | |
48 | + ids = chr(idx+ord('A')) | |
49 | + tail = os.path.join("annotation", ids) | |
50 | + return os.path.join(self.path, tail) | |
51 | + | |
52 | + def finito_path(self): | |
53 | + return os.path.join(self.path, "adjudication") | |
54 | + | |
55 | + def upload_path(self, file_name, idx): | |
56 | + return os.path.join(self.ann_path(idx), file_name) | |
57 | + | |
58 | + def upload_prim_path(self, file_name): | |
59 | + return os.path.join(self.finito_path(), file_name) | |
60 | + | |
61 | + def read_data(self, path): | |
62 | + with open(path) as src: | |
63 | + return src.read() | |
64 | + | |
65 | + def read_contents(self, path, exts): | |
66 | + contents = {} | |
67 | + for ext in exts: | |
68 | + contents[ext] = self.read_data(path + ext) | |
69 | + return contents | |
70 | + | |
71 | + def write_data(self, path, data): | |
72 | + with open(path, "w") as dest: | |
73 | + dest.write(data) | |
74 | + | |
75 | + def write_contents(self, path, contents): | |
76 | + for ext, data in contents.iteritems(): | |
77 | + dest_path = path + ext | |
78 | + self.write_data(dest_path, data) | |
79 | + if self.svn.info(dest_path) is None: | |
80 | + self.svn.add(dest_path) | |
81 | + | |
82 | + def upload(self, file_id, idx, contents): | |
83 | + self.write_contents(self.upload_path(file_id, idx), contents) | |
84 | + | |
85 | + def upload_prim(self, file_id, contents): | |
86 | + self.write_contents(self.upload_prim_path(file_id), contents) | |
87 | + | |
88 | + def download(self, file_id, exts): | |
89 | + return self.read_contents(os.path.join(self.new_path(), file_id), exts) | |
90 | + | |
91 | + def download_prim(self, file_id, exts, anno_per_file): | |
92 | + result = [] | |
93 | + for i in range(anno_per_file): | |
94 | + conts = self.read_contents(os.path.join(self.ann_path(i), file_id), exts) | |
95 | + result.append(conts) | |
96 | + return result | |
97 | + | |
98 | + def checkout(self, file_id, idx, exts): | |
99 | + src_paths = [ self.upload_path(file_id, idx) | |
100 | + , os.path.join(self.new_path(), file_id) ] | |
101 | + for path in src_paths: | |
102 | + if all(os.path.exists(path + ext) for ext in exts): | |
103 | + return self.read_contents(path, exts) | |
104 | + | |
105 | + def checkout_prim(self, file_id, exts): | |
106 | + path = os.path.join(self.finito_path(), file_id) | |
107 | + if all(os.path.exists(path + ext) for ext in exts): | |
108 | + return self.read_contents(path, exts) | |
109 | + | |
110 | + def commit(self, message): | |
111 | + self.svn.checkin(self.path, message, recurse=True) | |
112 | + | |
113 | + def revert(self): | |
114 | + self.svn.revert(self.path, recurse=True) | |
... | ... |
dfs/server.py
0 → 100644
1 | +++ a/dfs/server.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import sys | |
3 | +import os | |
4 | +import traceback | |
5 | +import socket, ssl | |
6 | +import random | |
7 | +from optparse import OptionParser | |
8 | +import signal | |
9 | + | |
10 | +from utils import validate_user, UserInvalid, send_contents, receive_contents | |
11 | +from msg.stream import read_msg, write_msg, accept_msg, EndOfStream, BadMessage | |
12 | +from msg.message import * | |
13 | +from repo import Repo | |
14 | +from database import Database | |
15 | +from config import Config | |
16 | + | |
17 | +class SSLServer: | |
18 | + | |
19 | + def __init__(self, host, port, backlog, cert_file, key_file, | |
20 | + repository, svn_login, svn_passwd, pass_file, | |
21 | + users_file, file_exts, anno_per_file, log=None): | |
22 | + """ | |
23 | + Initialize SSL server. | |
24 | + | |
25 | + params: | |
26 | + ======= | |
27 | + host : str | |
28 | + Host name. | |
29 | + port : int | |
30 | + Port number. | |
31 | + backlog : int | |
32 | + Maximum number of waiting connections. | |
33 | + cert_file : path | |
34 | + Public PEM certificate. | |
35 | + key_file : path | |
36 | + Private key. | |
37 | + pass_file : path | |
38 | + File with client passwords. | |
39 | + users_file : path | |
40 | + File with additional users configuration. | |
41 | + repository : path | |
42 | + Subversion working copy. | |
43 | + svn_login : str | |
44 | + Subversion login. | |
45 | + svn_passwd : str | |
46 | + Subversion password. | |
47 | + file_exts : [str] | |
48 | + List of file extensions. | |
49 | + anno_per_file : int | |
50 | + Number of normal annotators per file (1 or 2) | |
51 | + """ | |
52 | + self.cert_file = cert_file | |
53 | + self.key_file = key_file | |
54 | + self.pass_file = pass_file | |
55 | + self.users_file = users_file | |
56 | + self.wc = Repo(repository, svn_login, svn_passwd) | |
57 | + self.file_exts = file_exts | |
58 | + if log is not None: | |
59 | + self.log = open(log, "a") | |
60 | + else: | |
61 | + self.log = sys.stdout | |
62 | + self.bound = bind_socket(host, port, backlog=backlog) | |
63 | + self.serving = None | |
64 | + self.anno_per_file = anno_per_file | |
65 | + | |
66 | + def run(self): | |
67 | + while True: | |
68 | + newsocket, fromaddr = self.bound.accept() | |
69 | + self.serving = fromaddr | |
70 | + connstream = None | |
71 | + try: | |
72 | + connstream = ssl.wrap_socket(newsocket, | |
73 | + server_side=True, | |
74 | + certfile=self.cert_file, | |
75 | + keyfile=self.key_file, | |
76 | + ssl_version=ssl.PROTOCOL_TLSv1) | |
77 | + | |
78 | + login = validate_user(connstream, self.pass_file) | |
79 | + self.serve_client(connstream, login) | |
80 | + except UserInvalid as login: | |
81 | + print >> self.log, "AUTH ERROR:", login | |
82 | + except BadMessage as info: | |
83 | + print >> self.log, "BAD MESSAGE ERROR:", info | |
84 | + print >> self.log, traceback.format_exc().strip() | |
85 | + except EndOfStream: | |
86 | + print >> self.log, "UNEXPECTED END OF STREAM:" | |
87 | + print >> self.log, traceback.format_exc().strip() | |
88 | + except: | |
89 | + print >> self.log, "UNEXPECTED ERROR:" | |
90 | + print >> self.log, traceback.format_exc().strip() | |
91 | + finally: | |
92 | + try: | |
93 | + if not connstream is None: | |
94 | + connstream.shutdown(socket.SHUT_RDWR) | |
95 | + except: | |
96 | + print >> self.log, "UNEXPECTED ERROR:" | |
97 | + print >> self.log, traceback.format_exc().strip() | |
98 | + finally: | |
99 | + if not connstream is None: | |
100 | + connstream.close() | |
101 | + self.serving = None | |
102 | + | |
103 | + def exit(self): | |
104 | + if self.serving is not None: | |
105 | + print ("Client from %s connected to the server" | |
106 | + % str(self.serving)) | |
107 | + else: | |
108 | + sys.exit(0) | |
109 | + | |
110 | + def process_msg(self, msg, stream, login): | |
111 | + if type(msg) == UploadRequest: | |
112 | + self.process_upload_msg(msg, stream, login, checkin=False) | |
113 | + elif type(msg) == CheckinRequest: | |
114 | + self.process_upload_msg(msg, stream, login, checkin=True) | |
115 | + elif type(msg) == CheckoutRequest: | |
116 | + self.process_checkout_msg(msg, stream, login) | |
117 | + elif type(msg) == DownloadRequest: | |
118 | + self.process_download_msg(msg, stream, login) | |
119 | + elif type(msg) == DownloadPrimRequest: | |
120 | + self.process_download_prim_msg(msg, stream, login) | |
121 | + elif type(msg) == ReturnRequest: | |
122 | + self.process_return_msg(msg, stream, login) | |
123 | + elif type(msg) == StatsRequest: | |
124 | + self.process_stats_msg(msg, stream, login) | |
125 | + else: | |
126 | + print >> self.log, login, "sent:", msg | |
127 | + | |
128 | + def process_return_msg(self, msg, stream, login): | |
129 | + reason = accept_msg(stream, Message).get_contents().decode("utf-8") | |
130 | + | |
131 | + usr_cfg = Config(self.users_file) | |
132 | + try: | |
133 | + user_adj = login in usr_cfg["auth.adjudicators"].split() | |
134 | + except KeyError: | |
135 | + user_adj = False | |
136 | + | |
137 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
138 | + n = msg.get_number() | |
139 | + for _ in range(n): | |
140 | + file_id = accept_msg(stream, Message).get_contents() | |
141 | + is_adj = accept_msg(stream, BoolMessage).get_boolean() | |
142 | + if is_adj and not user_adj: | |
143 | + write_msg(stream, KoMessage()) | |
144 | + print >> self.log, "Upload error - user " + login + " tried to upload super annotated file " + file_id + " but is not a super annotator" | |
145 | + continue | |
146 | + else: | |
147 | + error = db.upload_prevention(file_id, login, is_adj) | |
148 | + if error is not None: | |
149 | + write_msg(stream, KoMessage()) | |
150 | + print >> self.log, "Upload error by user " + login + ". Details: "+error | |
151 | + continue | |
152 | + | |
153 | + write_msg(stream, OkMessage()) | |
154 | + contents = receive_contents(stream) | |
155 | + for key in contents.keys(): | |
156 | + if key not in self.file_exts: | |
157 | + print >> self.log, "Skipped file uploaded by user " + login + ". Filename:"+file_id+key | |
158 | + del contents[key] | |
159 | + | |
160 | + if is_adj is False: | |
161 | + idx = db.return_file(file_id, login, reason) | |
162 | + self.wc.upload(file_id, idx, contents) | |
163 | + else: | |
164 | + db.return_file_prim(file_id, login, reason) | |
165 | + self.wc.upload_prim(file_id, contents) | |
166 | + | |
167 | + db.save() | |
168 | + accept_msg(stream, ClientDone) | |
169 | + self.wc.commit("return request from %s" | |
170 | + % (login)) | |
171 | + write_msg(stream, ServerDone()) | |
172 | + | |
173 | + | |
174 | + def process_upload_msg(self, msg, stream, login, checkin=False): | |
175 | + usr_cfg = Config(self.users_file) | |
176 | + try: | |
177 | + user_adj = login in usr_cfg["auth.adjudicators"].split() | |
178 | + except KeyError: | |
179 | + user_adj = False | |
180 | + | |
181 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
182 | + n = msg.get_number() | |
183 | + for _ in range(n): | |
184 | + file_id = accept_msg(stream, Message).get_contents() | |
185 | + is_adj = accept_msg(stream, BoolMessage).get_boolean() | |
186 | + if is_adj and not user_adj: | |
187 | + write_msg(stream, KoMessage()) | |
188 | + print >> self.log, "Upload error - user " + login + " tried to upload super annotated file " + file_id + " but is not a super annotator" | |
189 | + continue | |
190 | + else: | |
191 | + error = db.upload_prevention(file_id, login, is_adj) | |
192 | + if error is not None: | |
193 | + write_msg(stream, KoMessage()) | |
194 | + print >> self.log, "Upload error by user " + login + ". Details: "+error | |
195 | + continue | |
196 | + | |
197 | + write_msg(stream, OkMessage()) | |
198 | + contents = receive_contents(stream) | |
199 | + for key in contents.keys(): | |
200 | + if key not in self.file_exts: | |
201 | + print >> self.log, "Skipped file uploaded by user " + login + ". Filename:"+file_id+key | |
202 | + del contents[key] | |
203 | + | |
204 | + if is_adj is False: | |
205 | + if checkin: | |
206 | + idx = db.upload(file_id, login) | |
207 | + else: | |
208 | + idx = db.upload_id(file_id, login) | |
209 | + self.wc.upload(file_id, idx, contents) | |
210 | + else: | |
211 | + if checkin: | |
212 | + db.upload_prim(file_id, login) | |
213 | + self.wc.upload_prim(file_id, contents) | |
214 | + | |
215 | + if checkin: | |
216 | + db.save() | |
217 | + accept_msg(stream, ClientDone) | |
218 | + self.wc.commit("%s request from %s" | |
219 | + % ("checkin" if checkin else "upload", login)) | |
220 | + write_msg(stream, ServerDone()) | |
221 | + | |
222 | + def process_checkout_msg(self, msg, stream, login): | |
223 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
224 | + | |
225 | + ann_files = db.owns_normal(login) | |
226 | + write_msg(stream, NumMessage(len(ann_files))) | |
227 | + for file_id in ann_files: | |
228 | + write_msg(stream, Message(file_id)) | |
229 | + idx = db.upload_id(file_id, login) | |
230 | + contents = self.wc.checkout(file_id, idx, self.file_exts) | |
231 | + send_contents(stream, contents) | |
232 | + | |
233 | + adj_files = db.owns_super(login) | |
234 | + write_msg(stream, NumMessage(len(adj_files))) | |
235 | + write_msg(stream, NumMessage(self.anno_per_file)) | |
236 | + for file_id in adj_files: | |
237 | + write_msg(stream, Message(file_id)) | |
238 | + | |
239 | + contents = self.wc.download_prim(file_id, self.file_exts, self.anno_per_file) | |
240 | + for conts in contents: | |
241 | + send_contents(stream, conts) | |
242 | + | |
243 | + conts3 = self.wc.checkout_prim(file_id, self.file_exts) | |
244 | + if conts3 is None: | |
245 | + write_msg(stream, BoolMessage(False)) | |
246 | + else: | |
247 | + write_msg(stream, BoolMessage(True)) | |
248 | + send_contents(stream, conts3) | |
249 | + | |
250 | + accept_msg(stream, ClientDone) | |
251 | + #self.wc.commit("checkout request from %s" % login) | |
252 | + write_msg(stream, ServerDone()) | |
253 | + | |
254 | + def process_stats_msg(self, msg, stream, login): | |
255 | + usr_cfg = Config(self.users_file) | |
256 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
257 | + | |
258 | + write_msg(stream, NumMessage(db.finished_count(login))) | |
259 | + | |
260 | + accept_msg(stream, ClientDone) | |
261 | + write_msg(stream, ServerDone()) | |
262 | + | |
263 | + def process_download_msg(self, msg, stream, login): | |
264 | + usr_cfg = Config(self.users_file) | |
265 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
266 | + | |
267 | + down_num = msg.get_number() | |
268 | + owns_num = len(db.owns_normal(login)) | |
269 | + limit = float('inf') | |
270 | + try: | |
271 | + limit = int(usr_cfg["limits.%s.annotation" % login]) | |
272 | + except KeyError: | |
273 | + limit = int(usr_cfg["limits.annotation"]) | |
274 | + if down_num + owns_num > limit: | |
275 | + down_num = max(0, limit - owns_num) | |
276 | + | |
277 | + for_ann_fixed, for_ann = db.for_annotation(login) | |
278 | + if len(for_ann) + len(for_ann_fixed) == 0: | |
279 | + print >> self.log, "User " + login + " has no more files to download for annotation." | |
280 | + | |
281 | + down_files = sample(for_ann_fixed, down_num) | |
282 | + down_num = max(0, down_num - len(down_files)) | |
283 | + down_files = down_files + sample(for_ann, down_num) | |
284 | + | |
285 | + write_msg(stream, NumMessage(len(down_files))) | |
286 | + | |
287 | + for file_id in down_files: | |
288 | + fixed_idx = db.download(file_id, login) | |
289 | + write_msg(stream, Message(file_id)) | |
290 | + if fixed_idx is None: | |
291 | + contents = self.wc.download(file_id, self.file_exts) | |
292 | + else: | |
293 | + contents = self.wc.checkout(file_id, fixed_idx, self.file_exts) | |
294 | + send_contents(stream, contents) | |
295 | + db.save() | |
296 | + accept_msg(stream, ClientDone) | |
297 | + self.wc.commit("download request from %s" % login) | |
298 | + write_msg(stream, ServerDone()) | |
299 | + | |
300 | + def process_download_prim_msg(self, msg, stream, login): | |
301 | + usr_cfg = Config(self.users_file) | |
302 | + try: | |
303 | + adjudicators = usr_cfg["auth.adjudicators"].split() | |
304 | + except KeyError: | |
305 | + adjudicators = [] | |
306 | + if login in adjudicators: | |
307 | + write_msg(stream, OkMessage()) | |
308 | + else: | |
309 | + write_msg(stream, KoMessage()) | |
310 | + return | |
311 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
312 | + | |
313 | + down_num = msg.get_number() | |
314 | + owns_num = len(db.owns_super(login)) | |
315 | + limit = float('inf') | |
316 | + try: | |
317 | + limit = int(usr_cfg["limits.%s.adjudication" % login]) | |
318 | + except KeyError: | |
319 | + limit = int(usr_cfg["limits.adjudication"]) | |
320 | + if down_num + owns_num > limit: | |
321 | + down_num = max(0, limit - owns_num) | |
322 | + | |
323 | + for_adj_fixed, for_adj = db.for_adjudication(login) | |
324 | + if len(for_adj) + len(for_adj_fixed) == 0: | |
325 | + print >> self.log, "User " + login + " has no more files to download for superannotation." | |
326 | + | |
327 | + down_files = sample(for_adj_fixed, down_num) | |
328 | + down_num = max(0, down_num - len(down_files)) | |
329 | + down_files = down_files + sample(for_adj, down_num) | |
330 | + | |
331 | + write_msg(stream, NumMessage(len(down_files))) | |
332 | + write_msg(stream, NumMessage(self.anno_per_file)) | |
333 | + for file_id in down_files: | |
334 | + write_msg(stream, Message(file_id)) | |
335 | + fixed = db.download_prim(file_id, login) | |
336 | + | |
337 | + contents = self.wc.download_prim(file_id, self.file_exts, self.anno_per_file) | |
338 | + for conts in contents: | |
339 | + send_contents(stream, conts) | |
340 | + | |
341 | + if fixed: | |
342 | + write_msg(stream, BoolMessage(True)) | |
343 | + contents = self.wc.checkout_prim(file_id, self.file_exts) | |
344 | + send_contents(stream, contents) | |
345 | + else: | |
346 | + write_msg(stream, BoolMessage(False)) | |
347 | + | |
348 | + db.save() | |
349 | + accept_msg(stream, ClientDone) | |
350 | + self.wc.commit("download' request from %s" % login) | |
351 | + write_msg(stream, ServerDone()) | |
352 | + | |
353 | + def serve_client(self, connstream, login): | |
354 | + msg = read_msg(connstream) | |
355 | + try: | |
356 | + self.process_msg(msg, connstream, login) | |
357 | + except: | |
358 | + self.wc.revert() | |
359 | + raise | |
360 | + | |
361 | +def bind_socket(host, port, backlog=5): | |
362 | + bound = socket.socket() | |
363 | + bound.bind((host, port)) | |
364 | + bound.listen(backlog) | |
365 | + return bound | |
366 | + | |
367 | +def sample(population, n): | |
368 | + if len(population) > n: | |
369 | + return random.sample(population, n) | |
370 | + else: | |
371 | + return population | |
372 | + | |
373 | +if __name__ == "__main__": | |
374 | + optparser = OptionParser(usage="""usage: %prog CONFIG""") | |
375 | + (options, args) = optparser.parse_args() | |
376 | + if len(args) != 1: | |
377 | + optparser.print_help() | |
378 | + sys.exit(0) | |
379 | + | |
380 | + cfg = Config(args[0]) | |
381 | + server = SSLServer( | |
382 | + host=cfg["connection.host"], | |
383 | + port=int(cfg["connection.port"]), | |
384 | + backlog=int(cfg["connection.backlog"]), | |
385 | + cert_file=cfg["connection.certfile"], | |
386 | + key_file=cfg["connection.keyfile"], | |
387 | + repository=cfg["svn.repository"], | |
388 | + svn_login=cfg["svn.login"], | |
389 | + svn_passwd=cfg["svn.passwd"], | |
390 | + pass_file=cfg["users.passfile"], | |
391 | + users_file=cfg["users.config"], | |
392 | + file_exts=cfg["file_extensions"].split(), | |
393 | + anno_per_file=int(cfg["anno_per_file"]) | |
394 | + ) | |
395 | + | |
396 | + def handler(signum, frame): | |
397 | + server.exit() | |
398 | + | |
399 | + signal.signal(signal.SIGINT, handler) | |
400 | + | |
401 | + server.run() | |
... | ... |
dfs/utils.py
0 → 100644
1 | +++ a/dfs/utils.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import hashlib | |
3 | + | |
4 | +from msg.stream import accept_msg, write_msg | |
5 | +from msg.message import Message, OkMessage, KoMessage, NumMessage | |
6 | + | |
7 | +class UserInvalid(Exception): | |
8 | + pass | |
9 | + | |
10 | +def validate_user(conns, pass_file): | |
11 | + pass_dict = parse_passwd(pass_file) | |
12 | + login = accept_msg(conns, Message).get_contents() | |
13 | + passwd = accept_msg(conns, Message).get_contents() | |
14 | + if pass_dict.has_key(login) and pass_dict[login] == passwd: | |
15 | + write_msg(conns, OkMessage()) | |
16 | + return login | |
17 | + else: | |
18 | + write_msg(conns, KoMessage()) | |
19 | + raise UserInvalid(login) | |
20 | + | |
21 | +def parse_passwd(pass_file): | |
22 | + result = {} | |
23 | + with open(pass_file) as f: | |
24 | + for line in f: | |
25 | + login, passwd = line.split("=") | |
26 | + login = login.strip() | |
27 | + passwd = passwd.strip() | |
28 | + result[login] = passwd | |
29 | + return result | |
30 | + | |
31 | +def _check_sum(*args): | |
32 | + m = hashlib.sha256() | |
33 | + for arg in args: | |
34 | + m.update(arg) | |
35 | + return m | |
36 | + | |
37 | +def check_sum(*args): | |
38 | + return _check_sum(*args).digest() | |
39 | + | |
40 | +def check_hexsum(*args): | |
41 | + return _check_sum(*args).hexdigest() | |
42 | + | |
43 | +def send_contents(stream, contents): | |
44 | + write_msg(stream, NumMessage(len(contents))) | |
45 | + for ext, data in contents.iteritems(): | |
46 | + write_msg(stream, Message(ext)) | |
47 | + write_msg(stream, Message(data)) | |
48 | + write_msg(stream, Message(check_sum(ext, data))) | |
49 | + accept_msg(stream, OkMessage) | |
50 | + | |
51 | +def receive_contents(stream): | |
52 | + contents = {} | |
53 | + n = accept_msg(stream, NumMessage).get_number() | |
54 | + for _ in range(n): | |
55 | + ext = accept_msg(stream, Message).get_contents() | |
56 | + data = accept_msg(stream, Message).get_contents() | |
57 | + csum = accept_msg(stream, Message).get_contents() | |
58 | + if csum == check_sum(ext, data): | |
59 | + write_msg(stream, OkMessage()) | |
60 | + else: | |
61 | + write_msg(stream, KoMessage()) | |
62 | + contents[ext] = data | |
63 | + return contents | |
... | ... |
example_configuration/passwd
0 → 100644
example_configuration/server.cfg
0 → 100644
1 | +++ a/example_configuration/server.cfg | |
1 | +file_extensions = .mmax _words.xml _mentions.xml | |
2 | +anno_per_file = 2 | |
3 | + | |
4 | +[connection] | |
5 | +host = localhost | |
6 | +port = 2225 | |
7 | +backlog = 5 # maximum number of waiting connections | |
8 | +{paths} # paths relative to configuration file | |
9 | +certfile = ssl.conf/cert.pem | |
10 | +keyfile = ssl.conf/key.pem | |
11 | + | |
12 | + | |
13 | +[svn] | |
14 | +login = machine | |
15 | +passwd = asdasdT7 | |
16 | +{paths} | |
17 | +repository = ../data/repo_wc # path to working copy of repo | |
18 | + | |
19 | + | |
20 | +[users] | |
21 | +{paths} | |
22 | +config = users.cfg | |
23 | +passfile = passwd | |
... | ... |
example_configuration/ssl.conf/cert.pem
0 → 100644
1 | +++ a/example_configuration/ssl.conf/cert.pem | |
1 | +-----BEGIN CERTIFICATE----- | |
2 | +MIICkDCCAfmgAwIBAgIJAJPcESSEV86nMA0GCSqGSIb3DQEBBQUAMGExCzAJBgNV | |
3 | +BAYTAlBMMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdD | |
4 | +b21wYW55MSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlQGV4YW1wbGUuY29tMB4XDTEz | |
5 | +MDEyMzEyMjIwN1oXDTE0MDEyMzEyMjIwN1owYTELMAkGA1UEBhMCUEwxDTALBgNV | |
6 | +BAgMBFRlc3QxDTALBgNVBAcMBENpdHkxEDAOBgNVBAoMB0NvbXBhbnkxIjAgBgkq | |
7 | +hkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD | |
8 | +gY0AMIGJAoGBAKUG9QHjUiCSjcvFQl1OqUgZ8c4Vzm7m7Ua7D33YDnGo3QGuhPML | |
9 | +0JB+JPBG++MrPdQqMV8eeXe1dFlrm6SHnrEZIhwZg8iDnPhFKNjHiAkx0PCQdI93 | |
10 | +rAe4EcszkgbQCXXLGjQJNDikwGZ6ciylJ7C2XewWTlWjv7/LqMwhQotLAgMBAAGj | |
11 | +UDBOMB0GA1UdDgQWBBRLRvOFIo9LIeiT2ohQalEzy687ijAfBgNVHSMEGDAWgBRL | |
12 | +RvOFIo9LIeiT2ohQalEzy687ijAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA | |
13 | +A4GBAD1odtgH1kZ4a0TEbwjK2PaHTYyofkkgeXsGGnG3eMkwKQ7PHJarBl7GtxP5 | |
14 | +Ls91k/KvBuNqCM/lPmM94USDSXrKGe0Zzc9WZbDG9SXO23gGOwPl+Tg9v1ocRHd2 | |
15 | +dYYIgZM8/tLcsHyNNMd7c4BOMD4rzkjgbThFFMl/2NSkvrO7 | |
16 | +-----END CERTIFICATE----- | |
... | ... |
example_configuration/ssl.conf/key.pem
0 → 100644
1 | +++ a/example_configuration/ssl.conf/key.pem | |
1 | +-----BEGIN PRIVATE KEY----- | |
2 | +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAKUG9QHjUiCSjcvF | |
3 | +Ql1OqUgZ8c4Vzm7m7Ua7D33YDnGo3QGuhPML0JB+JPBG++MrPdQqMV8eeXe1dFlr | |
4 | +m6SHnrEZIhwZg8iDnPhFKNjHiAkx0PCQdI93rAe4EcszkgbQCXXLGjQJNDikwGZ6 | |
5 | +ciylJ7C2XewWTlWjv7/LqMwhQotLAgMBAAECgYEAjx9XggmqgoFX5K/xfIbqHZQS | |
6 | +uE8FN/2jL0Kwjs3AySZQdlLrDvbiLU6ZrqGBd2VbPBRTuVDuu7ltbNf8plbQcxaa | |
7 | +yaKfKHPRYYIh/EqdaqsZ2bHHEARi03iNLDnktYz49I0ebFqgz0BGZVR3xDsjCu40 | |
8 | +s9Tku7IQsDdO/zVvreECQQDXHW1vli7iuOmTYfwKqeIV7wFPj0vp8XrUVOKqjiG1 | |
9 | +Fdf2ZlLrzeR/HbPbd2SL6u1dMV1j8wH/yPNRzFqNwOXpAkEAxGR5DzqrEE2ihyKL | |
10 | +WzQn6tTTA9FI8WPbgXdTm/R3dev2OgMLZycHlSgptCJ1f+fMfGNhWhotg8kZfrO0 | |
11 | +9qnDEwJBAIpKrNQT5Mh0wBSz5WillmcrY9hV8yPOznw6pg2nmhvkkxYg6iYWE1N1 | |
12 | +MO9ibg1VVouW9McQrrRV57cqfPysiiECQHP63d9Xh1R+dKAXj3LSD0zIWMGlY81i | |
13 | +amw/uvmb2ryiF+xUhfRqATld2ZsOypM5ofJHgmOCmCR+q3a+y/zrbk8CQQC0jx/T | |
14 | +TYw9ULoUe0bdx9RfJkrCEr7/+/DLs4MFwPiQ/e8foHcUL+CM9EY/FkMfbWbKjtuQ | |
15 | +iKXS/h5qp6NFdNVj | |
16 | +-----END PRIVATE KEY----- | |
... | ... |
example_configuration/users.cfg
0 → 100644
1 | +++ a/example_configuration/users.cfg | |
1 | +[auth] | |
2 | +adjudicators = adj1 adj2 | |
3 | + | |
4 | +[limits] | |
5 | +# number of files downloaded for annotation | |
6 | +annotation = 10 | |
7 | +# number of file pairs download for adjudication | |
8 | +adjudication = 5 | |
9 | + | |
10 | +# limits per user | |
11 | +# adj1.annotation = 2 | |
12 | +# adj2.adjudication = 5 | |
... | ... |
gui/CheckoutNumberDialog.py
0 → 100644
1 | +++ a/gui/CheckoutNumberDialog.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +# generated by wxGlade 0.6.3 on Tue Sep 27 12:15:56 2011 | |
3 | + | |
4 | +import wx | |
5 | + | |
6 | +# begin wxGlade: dependencies | |
7 | +# end wxGlade | |
8 | + | |
9 | +# begin wxGlade: extracode | |
10 | + | |
11 | +# end wxGlade | |
12 | + | |
13 | +"""aktualnie nieużywane! """ | |
14 | + | |
15 | +class CheckoutNumberDialog(wx.Dialog): | |
16 | + def __init__(self, *args, **kwds): | |
17 | + # begin wxGlade: CheckoutNumberDialog.__init__ | |
18 | + kwds["style"] = wx.DEFAULT_DIALOG_STYLE | |
19 | + wx.Dialog.__init__(self, *args, **kwds) | |
20 | + self.label_4 = wx.StaticText(self, -1, "checkout files: ") | |
21 | + self.checkout_number = wx.SpinCtrl(self, -1, "1", min=1, max=300) | |
22 | + self.button_4 = wx.Button(self, wx.ID_YES, "OK") | |
23 | + self.button_5 = wx.Button(self, wx.ID_NO, "Cancel") | |
24 | + | |
25 | + self.__set_properties() | |
26 | + self.__do_layout() | |
27 | + | |
28 | + self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_YES) | |
29 | + self.Bind(wx.EVT_BUTTON, self.on_cancel, id=wx.ID_NO) | |
30 | + # end wxGlade | |
31 | + | |
32 | + def __set_properties(self): | |
33 | + # begin wxGlade: CheckoutNumberDialog.__set_properties | |
34 | + self.SetTitle("checkout") | |
35 | + self.SetSize((193, 79)) | |
36 | + # end wxGlade | |
37 | + | |
38 | + def __do_layout(self): | |
39 | + # begin wxGlade: CheckoutNumberDialog.__do_layout | |
40 | + sizer_8 = wx.BoxSizer(wx.VERTICAL) | |
41 | + grid_sizer_3 = wx.GridSizer(1, 2, 0, 0) | |
42 | + grid_sizer_2 = wx.GridSizer(1, 2, 0, 0) | |
43 | + grid_sizer_2.Add(self.label_4, 0, wx.ALIGN_CENTER_VERTICAL, 0) | |
44 | + grid_sizer_2.Add(self.checkout_number, 0, wx.ALIGN_CENTER_VERTICAL, 0) | |
45 | + sizer_8.Add(grid_sizer_2, 1, 0, 0) | |
46 | + grid_sizer_3.Add(self.button_4, 0, 0, 0) | |
47 | + grid_sizer_3.Add(self.button_5, 0, 0, 0) | |
48 | + sizer_8.Add(grid_sizer_3, 0, wx.BOTTOM, 0) | |
49 | + self.SetSizer(sizer_8) | |
50 | + self.Layout() | |
51 | + self.Centre() | |
52 | + # end wxGlade | |
53 | + | |
54 | + def on_ok(self, event): # wxGlade: CheckoutNumberDialog.<event_handler> | |
55 | + print "Event handler `on_ok' not implemented!" | |
56 | + event.Skip() | |
57 | + | |
58 | + def on_cancel(self, event): # wxGlade: CheckoutNumberDialog.<event_handler> | |
59 | + print "Event handler `on_cancel' not implemented!" | |
60 | + event.Skip() | |
61 | + | |
62 | +# end of class CheckoutNumberDialog | |
63 | + | |
64 | + | |
... | ... |
gui/ConfigDialog.py
0 → 100644
1 | +++ a/gui/ConfigDialog.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +# generated by wxGlade 0.6.3 on Tue Sep 27 14:54:14 2011 | |
3 | + | |
4 | +import wx | |
5 | + | |
6 | +import i18n | |
7 | +_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors | |
8 | + | |
9 | +# begin wxGlade: dependencies | |
10 | +# end wxGlade | |
11 | + | |
12 | +# begin wxGlade: extracode | |
13 | + | |
14 | +# end wxGlade | |
15 | + | |
16 | +class ConfigDialog(wx.Dialog): | |
17 | + def __init__(self, *args, **kwds): | |
18 | + # begin wxGlade: ConfigDialog.__init__ | |
19 | + kwds["style"] = wx.DEFAULT_DIALOG_STYLE | |
20 | + wx.Dialog.__init__(self, *args, **kwds) | |
21 | + self.label_2 = wx.StaticText(self, -1, _("login")+":") | |
22 | + self.login_ctrl = wx.TextCtrl(self, -1, "") | |
23 | + self.label_6 = wx.StaticText(self, -1, _("password")+":") | |
24 | + self.passwd_ctrl = wx.TextCtrl(self, -1, "", style=wx.TE_PASSWORD) | |
25 | + self.label_5 = wx.StaticText(self, -1, _("extensions")+":") | |
26 | + self.exts_ctrl = wx.TextCtrl(self, -1, "", size=(250,30)) | |
27 | + self.button_2 = wx.Button(self, wx.ID_OK, _("OK")) | |
28 | + | |
29 | + self.__set_properties() | |
30 | + self.__do_layout() | |
31 | + # end wxGlade | |
32 | + | |
33 | + def __set_properties(self): | |
34 | + # begin wxGlade: ConfigDialog.__set_properties | |
35 | + self.SetTitle(_("Directory configuration")) | |
36 | + self.label_2.SetToolTipString(_("Login supplied by system administrator")) | |
37 | + self.label_6.SetToolTipString(_("Password supplied by system administrator")) | |
38 | + self.label_5.SetToolTipString(_("List of space separated file extensions")) | |
39 | + self.button_2.SetMinSize((85, 29)) | |
40 | + self.button_2.SetFocus() | |
41 | + # end wxGlade | |
42 | + | |
43 | + def __do_layout(self): | |
44 | + # begin wxGlade: ConfigDialog.__do_layout | |
45 | + sizer_5 = wx.BoxSizer(wx.VERTICAL) | |
46 | + grid_sizer_4 = wx.GridSizer(3, 2, 0, 0) | |
47 | + grid_sizer_4.Add(self.label_2, 0, wx.ALIGN_CENTER_VERTICAL, 0) | |
48 | + grid_sizer_4.Add(self.login_ctrl, 0, wx.ALIGN_CENTER_VERTICAL, 0) | |
49 | + grid_sizer_4.Add(self.label_6, 0, wx.ALIGN_CENTER_VERTICAL, 0) | |
50 | + grid_sizer_4.Add(self.passwd_ctrl, 0, wx.ALIGN_CENTER_VERTICAL, 0) | |
51 | + grid_sizer_4.Add(self.label_5, 0, wx.ALIGN_CENTER_VERTICAL, 0) | |
52 | + grid_sizer_4.Add(self.exts_ctrl, 0, wx.ALIGN_CENTER_VERTICAL, 0) | |
53 | + sizer_5.Add(grid_sizer_4, 0, 0, 0) | |
54 | + sizer_5.Add(self.button_2, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) | |
55 | + self.SetSizer(sizer_5) | |
56 | + sizer_5.Fit(self) | |
57 | + self.Layout() | |
58 | + # end wxGlade | |
59 | + | |
60 | +# end of class ConfigDialog | |
61 | + | |
62 | + | |
... | ... |
gui/DownloadNumberDialog.py
0 → 100644
1 | +++ a/gui/DownloadNumberDialog.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +# generated by wxGlade 0.6.3 on Wed Sep 28 10:10:39 2011 | |
3 | + | |
4 | +import wx | |
5 | + | |
6 | +import i18n | |
7 | +_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors | |
8 | + | |
9 | +# begin wxGlade: dependencies | |
10 | +# end wxGlade | |
11 | + | |
12 | +# begin wxGlade: extracode | |
13 | + | |
14 | +# end wxGlade | |
15 | + | |
16 | +class DownloadNumberDialog(wx.Dialog): | |
17 | + def __init__(self, *args, **kwds): | |
18 | + # begin wxGlade: DownloadNumberDialog.__init__ | |
19 | + kwds["style"] = wx.DEFAULT_DIALOG_STYLE | |
20 | + wx.Dialog.__init__(self, *args, **kwds) | |
21 | + self.label_4 = wx.StaticText(self, -1, _("Download count")+":") | |
22 | + self.checkout_number = wx.SpinCtrl(self, -1, "1", min=1, max=300) | |
23 | + self.button_4 = wx.Button(self, wx.ID_YES, _("OK")) | |
24 | + self.button_5 = wx.Button(self, wx.ID_NO, _("Cancel")) | |
25 | + | |
26 | + self.__set_properties() | |
27 | + self.__do_layout() | |
28 | + | |
29 | + self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_YES) | |
30 | + self.Bind(wx.EVT_BUTTON, self.on_cancel, id=wx.ID_NO) | |
31 | + # end wxGlade | |
32 | + | |
33 | + def __set_properties(self): | |
34 | + # begin wxGlade: DownloadNumberDialog.__set_properties | |
35 | + self.SetTitle(_("Download")) | |
36 | + self.label_4.SetToolTipString(_("Number of files to download")) | |
37 | + # end wxGlade | |
38 | + | |
39 | + def __do_layout(self): | |
40 | + # begin wxGlade: DownloadNumberDialog.__do_layout | |
41 | + grid_sizer_2 = wx.GridSizer(2, 2, 20, 20) | |
42 | + grid_sizer_2.Add(self.label_4, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) | |
43 | + grid_sizer_2.Add(self.checkout_number, 0, 0, 0) | |
44 | + grid_sizer_2.Add(self.button_4, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) | |
45 | + grid_sizer_2.Add(self.button_5, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) | |
46 | + self.SetSizer(grid_sizer_2) | |
47 | + grid_sizer_2.Fit(self) | |
48 | + self.Layout() | |
49 | + self.Centre() | |
50 | + # end wxGlade | |
51 | + | |
52 | + def on_ok(self, event): # wxGlade: DownloadNumberDialog.<event_handler> | |
53 | + self.EndModal(wx.ID_OK) | |
54 | + | |
55 | + def on_cancel(self, event): # wxGlade: DownloadNumberDialog.<event_handler> | |
56 | + self.EndModal(wx.ID_CANCEL) | |
57 | + | |
58 | +# end of class DownloadNumberDialog | |
59 | + | |
60 | + | |
... | ... |
gui/LogDialog.py
0 → 100644
1 | +++ a/gui/LogDialog.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +# generated by wxGlade 0.6.3 on Tue Sep 27 12:15:56 2011 | |
3 | + | |
4 | +import wx | |
5 | + | |
6 | +# begin wxGlade: dependencies | |
7 | +# end wxGlade | |
8 | + | |
9 | +# begin wxGlade: extracode | |
10 | + | |
11 | +# end wxGlade | |
12 | + | |
13 | +"""aktualnie nieużywane! """ | |
14 | + | |
15 | +class LogDialog(wx.Dialog): | |
16 | + def __init__(self, *args, **kwds): | |
17 | + # begin wxGlade: LogDialog.__init__ | |
18 | + kwds["style"] = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.THICK_FRAME | |
19 | + wx.Dialog.__init__(self, *args, **kwds) | |
20 | + | |
21 | + self.__set_properties() | |
22 | + self.__do_layout() | |
23 | + # end wxGlade | |
24 | + | |
25 | + def __set_properties(self): | |
26 | + # begin wxGlade: LogDialog.__set_properties | |
27 | + self.SetTitle("Log") | |
28 | + self.SetSize((400, 315)) | |
29 | + # end wxGlade | |
30 | + | |
31 | + def __do_layout(self): | |
32 | + # begin wxGlade: LogDialog.__do_layout | |
33 | + self.Layout() | |
34 | + # end wxGlade | |
35 | + | |
36 | + def on_ok(self, event): # wxGlade: LogDialog.<event_handler> | |
37 | + print "Event handler `on_ok' not implemented!" | |
38 | + event.Skip() | |
39 | + | |
40 | +# end of class LogDialog | |
41 | + | |
42 | + | |
... | ... |
gui/LoginDialog.py
0 → 100644
1 | +++ a/gui/LoginDialog.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +# generated by wxGlade 0.6.3 on Tue Sep 27 12:15:56 2011 | |
3 | + | |
4 | +import wx | |
5 | + | |
6 | +# begin wxGlade: dependencies | |
7 | +# end wxGlade | |
8 | + | |
9 | +# begin wxGlade: extracode | |
10 | + | |
11 | +# end wxGlade | |
12 | + | |
13 | +"""aktualnie nieużywane! """ | |
14 | + | |
15 | +class LoginDialog(wx.Dialog): | |
16 | + def __init__(self, *args, **kwds): | |
17 | + # begin wxGlade: LoginDialog.__init__ | |
18 | + kwds["style"] = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.THICK_FRAME | |
19 | + wx.Dialog.__init__(self, *args, **kwds) | |
20 | + self.label_1 = wx.StaticText(self, -1, "login: ") | |
21 | + self.login_ctrl = wx.TextCtrl(self, -1, "") | |
22 | + self.label_3 = wx.StaticText(self, -1, "password: ") | |
23 | + self.passwd_ctrl = wx.TextCtrl(self, -1, "", style=wx.TE_PASSWORD) | |
24 | + self.save_passwd = wx.CheckBox(self, -1, "save password") | |
25 | + self.button_1 = wx.Button(self, wx.ID_OK, "OK") | |
26 | + | |
27 | + self.__set_properties() | |
28 | + self.__do_layout() | |
29 | + | |
30 | + self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK) | |
31 | + # end wxGlade | |
32 | + | |
33 | + def __set_properties(self): | |
34 | + # begin wxGlade: LoginDialog.__set_properties | |
35 | + self.SetTitle("login") | |
36 | + self.button_1.SetMinSize((85, 32)) | |
37 | + # end wxGlade | |
38 | + | |
39 | + def __do_layout(self): | |
40 | + # begin wxGlade: LoginDialog.__do_layout | |
41 | + sizer_4 = wx.BoxSizer(wx.VERTICAL) | |
42 | + grid_sizer_1 = wx.GridSizer(2, 2, 0, 0) | |
43 | + grid_sizer_1.Add(self.label_1, 0, wx.ALIGN_CENTER_VERTICAL, 0) | |
44 | + grid_sizer_1.Add(self.login_ctrl, 0, 0, 0) | |
45 | + grid_sizer_1.Add(self.label_3, 0, wx.ALIGN_CENTER_VERTICAL, 0) | |
46 | + grid_sizer_1.Add(self.passwd_ctrl, 0, 0, 0) | |
47 | + sizer_4.Add(grid_sizer_1, 0, 0, 0) | |
48 | + sizer_4.Add(self.save_passwd, 0, 0, 0) | |
49 | + sizer_4.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) | |
50 | + self.SetSizer(sizer_4) | |
51 | + sizer_4.Fit(self) | |
52 | + self.Layout() | |
53 | + # end wxGlade | |
54 | + | |
55 | + def on_ok(self, event): # wxGlade: LoginDialog.<event_handler> | |
56 | + print "Event handler `on_ok' not implemented!" | |
57 | + event.Skip() | |
58 | + | |
59 | +# end of class LoginDialog | |
60 | + | |
61 | + | |
... | ... |
gui/MainFrame.py
0 → 100644
1 | +++ a/gui/MainFrame.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +# generated by wxGlade 0.6.3 on Tue Sep 27 12:15:56 2011 | |
3 | + | |
4 | +import wx | |
5 | + | |
6 | +import i18n | |
7 | +_ = i18n.language.ugettext | |
8 | + | |
9 | +from DownloadNumberDialog import DownloadNumberDialog | |
10 | +from ReturnReasonDialog import ReturnReasonDialog | |
11 | +from SortableListCtrl import SortableListCtrl | |
12 | + | |
13 | +import const | |
14 | +import utils | |
15 | + | |
16 | +# begin wxGlade: dependencies | |
17 | +# end wxGlade | |
18 | + | |
19 | +# begin wxGlade: extracode | |
20 | + | |
21 | +# end wxGlade | |
22 | + | |
23 | +class MainFrame(wx.Frame): | |
24 | + def __init__(self, *args, **kwds): | |
25 | + # begin wxGlade: MainFrame.__init__ | |
26 | + kwds["style"] = wx.DEFAULT_FRAME_STYLE | |
27 | + wx.Frame.__init__(self, *args, **kwds) | |
28 | + self.window_1 = wx.SplitterWindow(self, -1, style=wx.SP_3D|wx.SP_BORDER) | |
29 | + self.window_1_pane_2 = wx.Panel(self.window_1, -1) | |
30 | + self.window_1_pane_1 = wx.Panel(self.window_1, -1) | |
31 | + self.notebook_1 = wx.Notebook(self.window_1_pane_1, -1, style=wx.BK_DEFAULT) | |
32 | + self.notebook_1_pane_1 = wx.Panel(self.notebook_1, -1) | |
33 | + self.notebook_1_pane_2 = wx.Panel(self.notebook_1, -1) | |
34 | + | |
35 | + self.log_ctrl = wx.TextCtrl(self.window_1_pane_2, -1, "", style=wx.TE_MULTILINE|wx.TE_READONLY) | |
36 | + | |
37 | + self.files_list = SortableListCtrl(self.notebook_1_pane_1, self.log_ctrl, False, const.program, const.extension) | |
38 | + self.super_files_list = SortableListCtrl(self.notebook_1_pane_2, self.log_ctrl, True, const.program, const.extension) | |
39 | + | |
40 | + # Menu Bar | |
41 | + self.frame_1_menubar = wx.MenuBar() | |
42 | + self.dir_menu = wx.Menu() | |
43 | + self.dir_menu_open = wx.MenuItem(self.dir_menu, 5, _("Open"), _("Open new directory"), wx.ITEM_NORMAL) | |
44 | + self.dir_menu.AppendItem(self.dir_menu_open) | |
45 | + self.dir_menu.Append(6, _("Reconfigure"), _("Reconfigure directory"), wx.ITEM_NORMAL) | |
46 | + self.dir_menu.AppendSeparator() | |
47 | + if not const.hide_dir_menu == "yes": | |
48 | + self.frame_1_menubar.Append(self.dir_menu, _("Directory")) | |
49 | + wxglade_tmp_menu = wx.Menu() | |
50 | + wxglade_tmp_menu.Append(1, _("Upload Selected"), _("Upload annotated or adjudicated files"), wx.ITEM_NORMAL) | |
51 | + wxglade_tmp_menu.Append(8, _("Checkin Selected"), _("Upload annotated or adjudicated; files will be deleted from local disk and you will not be able to modify them again"), wx.ITEM_NORMAL) | |
52 | + wxglade_tmp_menu.Append(10, _("Return Selected"), _("Return annotated or adjudicated because they are not suitable for annotation or contain errors"), wx.ITEM_NORMAL) | |
53 | + wxglade_tmp_menu.AppendSeparator() | |
54 | + wxglade_tmp_menu.Append(2, _("Download (annotation)"), _("Download files for annotation"), wx.ITEM_NORMAL) | |
55 | + wxglade_tmp_menu.Append(3, _("Download (adjudication)"), _("Download files for adjudication"), wx.ITEM_NORMAL) | |
56 | + wxglade_tmp_menu.AppendSeparator() | |
57 | + wxglade_tmp_menu.Append(7, _("Checkout"), _("Checkout files from server to your local, empty directory"), wx.ITEM_NORMAL) | |
58 | + | |
59 | + self.frame_1_menubar.Append(wxglade_tmp_menu, _("Files")) | |
60 | + wxglade_tmp_menu = wx.Menu() | |
61 | + wxglade_tmp_menu.Append(4, _("Ready"), _("Select items which are marked for upload"), wx.ITEM_NORMAL) | |
62 | + wxglade_tmp_menu.Append(9, _("Modified"), _("Select items which are modified"), wx.ITEM_NORMAL) | |
63 | + self.frame_1_menubar.Append(wxglade_tmp_menu, _("Select")) | |
64 | + | |
65 | + wxglade_tmp_menu = wx.Menu() | |
66 | + wxglade_tmp_menu.Append(11, _("Finished count"), _("Check number of finished texts"), wx.ITEM_NORMAL) | |
67 | + self.frame_1_menubar.Append(wxglade_tmp_menu, _("Annotation")) | |
68 | + | |
69 | + self.SetMenuBar(self.frame_1_menubar) | |
70 | + # Menu Bar end | |
71 | + | |
72 | + self.CreateStatusBar() | |
73 | + | |
74 | + self.__set_properties() | |
75 | + self.__do_layout() | |
76 | + | |
77 | + self.Bind(wx.EVT_MENU, self.open_directory, self.dir_menu_open) | |
78 | + self.Bind(wx.EVT_MENU, self.reconf_directory, id=6) | |
79 | + self.Bind(wx.EVT_MENU, self.upload, id=1) | |
80 | + self.Bind(wx.EVT_MENU, self.checkin, id=8) | |
81 | + self.Bind(wx.EVT_MENU, self.download, id=2) | |
82 | + self.Bind(wx.EVT_MENU, self.download_prim, id=3) | |
83 | + self.Bind(wx.EVT_MENU, self.checkout, id=7) | |
84 | + self.Bind(wx.EVT_MENU, self.select_ready, id=4) | |
85 | + self.Bind(wx.EVT_MENU, self.select_modified, id=9) | |
86 | + self.Bind(wx.EVT_MENU, self.return_files, id=10) | |
87 | + self.Bind(wx.EVT_MENU, self.check_stats, id=11) | |
88 | + | |
89 | + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.activate_item, self.files_list) | |
90 | + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.activate_super_item, self.super_files_list) | |
91 | + | |
92 | + def __set_properties(self): | |
93 | + # begin wxGlade: MainFrame.__set_properties | |
94 | + self.SetTitle("--") | |
95 | + self.SetSize((663, 556)) | |
96 | + self.SetFocus() | |
97 | + # end wxGlade | |
98 | + | |
99 | + def __do_layout(self): | |
100 | + # begin wxGlade: MainFrame.__do_layout | |
101 | + sizer_6 = wx.BoxSizer(wx.HORIZONTAL) | |
102 | + sizer_7 = wx.BoxSizer(wx.VERTICAL) | |
103 | + sizer_1 = wx.BoxSizer(wx.VERTICAL) | |
104 | + sizer_3 = wx.BoxSizer(wx.VERTICAL) | |
105 | + sizer_2 = wx.BoxSizer(wx.VERTICAL) | |
106 | + sizer_2.Add(self.files_list, 1, wx.EXPAND, 0) | |
107 | + self.notebook_1_pane_1.SetSizer(sizer_2) | |
108 | + sizer_3.Add(self.super_files_list, 1, wx.EXPAND, 0) | |
109 | + self.notebook_1_pane_2.SetSizer(sizer_3) | |
110 | + self.notebook_1.AddPage(self.notebook_1_pane_1, _("Annotation")) | |
111 | + self.notebook_1.AddPage(self.notebook_1_pane_2, _("Adjudication")) | |
112 | + sizer_1.Add(self.notebook_1, 1, wx.EXPAND, 0) | |
113 | + self.window_1_pane_1.SetSizer(sizer_1) | |
114 | + sizer_7.Add(self.log_ctrl, 1, wx.EXPAND, 0) | |
115 | + self.window_1_pane_2.SetSizer(sizer_7) | |
116 | + self.window_1.SplitHorizontally(self.window_1_pane_1, self.window_1_pane_2, -100) | |
117 | + sizer_6.Add(self.window_1, 1, wx.EXPAND, 0) | |
118 | + self.SetSizer(sizer_6) | |
119 | + self.Layout() | |
120 | + self.Centre() | |
121 | + # end wxGlade | |
122 | + | |
123 | + def set_manager(self, manager): | |
124 | + self.manager = manager | |
125 | + | |
126 | + def open_directory(self, event): # wxGlade: MainFrame.<event_handler> | |
127 | + if event.GetId() == self.dir_menu_open.GetId(): | |
128 | + dlg = wx.DirDialog(self, message=_("Choose a directory")) | |
129 | + try: | |
130 | + if dlg.ShowModal() == wx.ID_OK: | |
131 | + self.manager.set_directory(dlg.GetPath()) | |
132 | + finally: | |
133 | + dlg.Destroy() | |
134 | + else: | |
135 | + item = self.dir_menu.FindItemById(event.GetId()) | |
136 | + self.manager.set_directory(item.GetLabel()) | |
137 | + | |
138 | + def upload(self, event): # wxGlade: MainFrame.<event_handler> | |
139 | + names = ( self.files_list.get_selected() | |
140 | + + self.super_files_list.get_selected() ) | |
141 | + info = ( _("Do you want to upload the following files?")+"\n\n" | |
142 | + + "\n".join(names) ) | |
143 | + dial = wx.MessageDialog( self, info, _('Question') | |
144 | + , wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION ) | |
145 | + try: | |
146 | + if dial.ShowModal() == wx.ID_YES: | |
147 | + self.manager.upload(names, checkin=False) | |
148 | + finally: | |
149 | + dial.Destroy() | |
150 | + | |
151 | + def return_files(self, event): # wxGlade: MainFrame.<event_handler> | |
152 | + names = ( self.files_list.get_selected() | |
153 | + + self.super_files_list.get_selected() ) | |
154 | + reason = None | |
155 | + dialog = ReturnReasonDialog(self, names) | |
156 | + try: | |
157 | + if dialog.ShowModal() == wx.ID_OK: | |
158 | + reason = dialog.reason.GetValue().encode("utf-8") | |
159 | + finally: | |
160 | + dialog.Destroy() | |
161 | + if reason is not None: | |
162 | + self.manager.return_files(names, reason) | |
163 | + | |
164 | + def download(self, event): # wxGlade: MainFrame.<event_handler> | |
165 | + num = None | |
166 | + dialog = DownloadNumberDialog(self, -1) | |
167 | + try: | |
168 | + if dialog.ShowModal() == wx.ID_OK: | |
169 | + num = dialog.checkout_number.GetValue() | |
170 | + finally: | |
171 | + dialog.Destroy() | |
172 | + if num is not None: | |
173 | + self.manager.download(num) | |
174 | + | |
175 | + def download_prim(self, event): # wxGlade: MainFrame.<event_handler> | |
176 | + num = None | |
177 | + dialog = DownloadNumberDialog(self, -1) | |
178 | + try: | |
179 | + if dialog.ShowModal() == wx.ID_OK: | |
180 | + num = dialog.checkout_number.GetValue() | |
181 | + finally: | |
182 | + dialog.Destroy() | |
183 | + if num is not None: | |
184 | + self.manager.download_prim(num) | |
185 | + | |
186 | + def activate_item(self, event): # wxGlade: MainFrame.<event_handler> | |
187 | + utils.change_ready( self.manager.ready | |
188 | + , self.files_list | |
189 | + , event.GetIndex() ) | |
190 | + | |
191 | + def activate_super_item(self, event): # wxGlade: MainFrame.<event_handler> | |
192 | + utils.change_ready_prim( self.manager.ready | |
193 | + , self.super_files_list | |
194 | + , event.GetIndex() ) | |
195 | + | |
196 | + def reconf_directory(self, event): # wxGlade: MainFrame.<event_handler> | |
197 | + self.manager.reconf_directory() | |
198 | + | |
199 | + def select_ready(self, event): # wxGlade: MainFrame.<event_handler> | |
200 | + self.files_list.select_ready() | |
201 | + self.super_files_list.select_ready() | |
202 | + | |
203 | + def checkout(self, event): # wxGlade: MainFrame.<event_handler> | |
204 | + self.manager.checkout() | |
205 | + | |
206 | + def checkin(self, event): # wxGlade: MainFrame.<event_handler> | |
207 | + names = ( self.files_list.get_selected() | |
208 | + + self.super_files_list.get_selected() ) | |
209 | + info = ( _("Do you want to checkin the following files?")+"\n\n" | |
210 | + + "\n".join(names) | |
211 | + + "\n\n"+_("Files will be deleted from the local directory and you will not be able to read, write or modify them later.")) | |
212 | + dial = wx.MessageDialog( self, info, _('Question') | |
213 | + , wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION ) | |
214 | + try: | |
215 | + if dial.ShowModal() == wx.ID_YES: | |
216 | + self.manager.upload(names, checkin=True) | |
217 | + finally: | |
218 | + dial.Destroy() | |
219 | + | |
220 | + def select_modified(self, event): # wxGlade: MainFrame.<event_handler> | |
221 | + self.files_list.select_modified() | |
222 | + self.super_files_list.select_modified() | |
223 | + | |
224 | + def check_stats(self, event): # wxGlade: MainFrame.<event_handler> | |
225 | + self.manager.check_stats() | |
226 | + | |
227 | +# end of class MainFrame | |
... | ... |
gui/ReturnReasonDialog.py
0 → 100644
1 | +++ a/gui/ReturnReasonDialog.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +# generated by wxGlade 0.6.3 on Wed Sep 28 10:10:39 2011 | |
3 | + | |
4 | +import wx | |
5 | + | |
6 | +import i18n | |
7 | +_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors | |
8 | + | |
9 | +# begin wxGlade: dependencies | |
10 | +# end wxGlade | |
11 | + | |
12 | +# begin wxGlade: extracode | |
13 | + | |
14 | +# end wxGlade | |
15 | + | |
16 | +class ReturnReasonDialog(wx.Dialog): | |
17 | + def __init__(self, parent, names): | |
18 | + # begin wxGlade: ReturnReasonDialog.__init__ | |
19 | + wx.Dialog.__init__(self, parent, -1) | |
20 | + | |
21 | + info = ( _("Why do you want to return the following files?")+"\n" | |
22 | + + "\n".join(names) ) | |
23 | + | |
24 | + self.label_1 = wx.StaticText(self, -1, info) | |
25 | + self.label_4 = wx.StaticText(self, -1, _("Reason")+":") | |
26 | + self.reason = wx.TextCtrl(self, -1, "", size=(250, 80), style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) | |
27 | + self.button_4 = wx.Button(self, wx.ID_YES, _("OK")) | |
28 | + self.button_5 = wx.Button(self, wx.ID_NO, _("Cancel")) | |
29 | + | |
30 | + self.__set_properties() | |
31 | + self.__do_layout() | |
32 | + | |
33 | + self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_YES) | |
34 | + self.Bind(wx.EVT_BUTTON, self.on_cancel, id=wx.ID_NO) | |
35 | + # end wxGlade | |
36 | + | |
37 | + def __set_properties(self): | |
38 | + # begin wxGlade: ReturnReasonDialog.__set_properties | |
39 | + self.SetTitle(_("Return files")) | |
40 | + # end wxGlade | |
41 | + | |
42 | + def __do_layout(self): | |
43 | + # begin wxGlade: ReturnReasonDialog.__do_layout | |
44 | + sizer_2 = wx.BoxSizer(wx.HORIZONTAL) | |
45 | + sizer_2.Add(self.label_4, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) | |
46 | + sizer_2.Add(self.reason, 0, 0, 0) | |
47 | + | |
48 | + sizer_3 = wx.BoxSizer(wx.HORIZONTAL) | |
49 | + sizer_3.Add(self.button_4, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) | |
50 | + sizer_3.Add(self.button_5, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) | |
51 | + | |
52 | + sizer_1 = wx.BoxSizer(wx.VERTICAL) | |
53 | + sizer_1.Add(self.label_1, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 1) | |
54 | + sizer_1.Add(sizer_2, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 1) | |
55 | + sizer_1.Add(sizer_3, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 1) | |
56 | + self.SetSizer(sizer_1) | |
57 | + sizer_1.Fit(self) | |
58 | + self.Layout() | |
59 | + # end wxGlade | |
60 | + | |
61 | + def on_ok(self, event): # wxGlade: ReturnReasonDialog.<event_handler> | |
62 | + self.EndModal(wx.ID_OK) | |
63 | + | |
64 | + def on_cancel(self, event): # wxGlade: ReturnReasonDialog.<event_handler> | |
65 | + self.EndModal(wx.ID_CANCEL) | |
66 | + | |
67 | +# end of class ReturnReasonDialog | |
68 | + | |
69 | + | |
... | ... |
gui/SortableListCtrl.py
0 → 100644
1 | +++ a/gui/SortableListCtrl.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import threading | |
3 | +from PIL import Image | |
4 | + | |
5 | +import i18n | |
6 | +_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors | |
7 | + | |
8 | +import os.path | |
9 | +import wx | |
10 | +import wx.lib.mixins.listctrl as listmix | |
11 | +import subprocess | |
12 | + | |
13 | +try: | |
14 | + from agw import ultimatelistctrl as ULC | |
15 | +except ImportError: # if it's not there locally, try the wxPython lib. | |
16 | + from wx.lib.agw import ultimatelistctrl as ULC | |
17 | + | |
18 | +class SortableListCtrl(ULC.UltimateListCtrl, listmix.ColumnSorterMixin, listmix.ListCtrlAutoWidthMixin): | |
19 | + def __init__(self, parent, log, superann, program, extension): | |
20 | + | |
21 | + self._lock = threading.Lock() | |
22 | + | |
23 | + self.program = program | |
24 | + self.extension = extension | |
25 | + | |
26 | + ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_REPORT | ULC.ULC_HAS_VARIABLE_ROW_HEIGHT | ULC.ULC_USER_ROW_HEIGHT)#| wx.LC_HRULES | wx.LC_VRULES ) | |
27 | + listmix.ListCtrlAutoWidthMixin.__init__(self) | |
28 | + | |
29 | + self.log_ctrl = log | |
30 | + | |
31 | + self.il = wx.ImageList(16, 16) | |
32 | + up = wx.Image(os.path.join('img', 'up.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap() | |
33 | + down = wx.Image(os.path.join('img', 'down.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap() | |
34 | + if superann: | |
35 | + textimg_1 = wx.Image(os.path.join('img', 'textimg_2.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap() | |
36 | + else: | |
37 | + textimg_1 = wx.Image(os.path.join('img', 'textimg_1.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap() | |
38 | + ready = wx.Image(os.path.join('img', 'ready.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap() | |
39 | + self.sm_up = self.il.Add(up) | |
40 | + self.sm_dn = self.il.Add(down) | |
41 | + self.textimg_1 = self.il.Add(textimg_1) | |
42 | + self.ready = self.il.Add(ready) | |
43 | + | |
44 | + self.AssignImageList(self.il, wx.IMAGE_LIST_SMALL) | |
45 | + | |
46 | + self.SetUserLineHeight(23) | |
47 | + self.InsertColumn(0, _("File")) | |
48 | + self.InsertColumn(1, _("Status")) | |
49 | + self.InsertColumn(2, _("Ready")) | |
50 | + self.InsertColumn(3, _("Stage")) | |
51 | + self.InsertColumn(4, "") | |
52 | + self.SetColumnWidth(0, 80) | |
53 | + self.SetColumnWidth(1, 100) | |
54 | + self.SetColumnWidth(2, 120) | |
55 | + self.SetColumnWidth(3, 100) | |
56 | + | |
57 | + self.itemDataMap = {} | |
58 | + | |
59 | + listmix.ColumnSorterMixin.__init__(self, 5) | |
60 | + | |
61 | + # required by ColumnSorterMixin | |
62 | + def GetListCtrl(self): | |
63 | + return self | |
64 | + | |
65 | + def GetSortImages(self): | |
66 | + return (self.sm_dn, self.sm_up) | |
67 | + # required by ColumnSorterMixin - end | |
68 | + | |
69 | + def show(self, data): | |
70 | + self._lock.acquire() | |
71 | + try: | |
72 | + # for some reason deleteAllItems() doesn't work with buttons inside ulc | |
73 | + for item in range(self.GetItemCount()): | |
74 | + self.DeleteItem(0) | |
75 | + self.itemDataMap.clear() | |
76 | + for i, (file_name, status, is_ready, stage) in enumerate(data): | |
77 | + img = self.textimg_1 | |
78 | + if is_ready == _("Yes"): | |
79 | + img = self.ready | |
80 | + pos = self.InsertImageStringItem(i, file_name, img) | |
81 | + self.SetStringItem(pos, 1, status) | |
82 | + self.SetStringItem(pos, 2, is_ready) | |
83 | + self.SetStringItem(pos, 3, stage) | |
84 | + | |
85 | + button = wx.Button(self, id=i, size=(80, 23), label=_("Open")) | |
86 | + self.SetItemWindow(pos, col=4, wnd=button, expand=False) | |
87 | + button.Bind(wx.EVT_BUTTON, self.open_button) | |
88 | + | |
89 | + self.SetItemData(pos, i) | |
90 | + | |
91 | + self.itemDataMap[i] = (file_name, status, is_ready, stage, None) | |
92 | + finally: | |
93 | + self._lock.release() | |
94 | + | |
95 | + def shown_data(self): | |
96 | + result = [] | |
97 | + for i in range(self.GetItemCount()): | |
98 | + part_result = [] | |
99 | + for j in range(0, 4): | |
100 | + part_result.append(self.GetItem(i, j).GetText()) | |
101 | + result.append(tuple(part_result)) | |
102 | + return result | |
103 | + | |
104 | + def SetReadyYes(self, pos): | |
105 | + self.SetItemImage(pos, self.ready) | |
106 | + return self._set_ready(pos, _("Yes")) | |
107 | + | |
108 | + def SetReadyNo(self, pos): | |
109 | + self.SetItemImage(pos, self.textimg_1) | |
110 | + return self._set_ready(pos, _("No")) | |
111 | + | |
112 | + def _set_ready(self, pos, val): | |
113 | + file_name = self.GetItem(pos, 0).GetText() | |
114 | + for key, (filename, status, ready, stage, sth) in self.itemDataMap.iteritems(): | |
115 | + if filename == file_name: | |
116 | + self.itemDataMap[key] = (filename, status, val, stage, sth) | |
117 | + break | |
118 | + return self.SetStringItem(pos, 2, val) | |
119 | + | |
120 | + def GetFilename(self, pos): | |
121 | + return self.GetItem(pos, 0).GetText() | |
122 | + | |
123 | + def IsReady(self, pos): | |
124 | + return self.GetItem(pos, 2).GetText() == _("Yes") | |
125 | + | |
126 | + def select_ready(self): | |
127 | + return self._select(lambda list, pos : list.GetItem(pos, 2).GetText() == _("Yes")) | |
128 | + | |
129 | + def select_modified(self): | |
130 | + return self._select(lambda list, pos : list.GetItem(pos, 1).GetText() == _("Modified")) | |
131 | + | |
132 | + def _select(self, cond): | |
133 | + for i in range(self.GetItemCount()): | |
134 | + if cond(self, i): | |
135 | + self.Select(i, 1) | |
136 | + else: | |
137 | + self.Select(i, 0) | |
138 | + | |
139 | + def get_selected(self): | |
140 | + fnames = [] | |
141 | + for i in range(self.GetItemCount()): | |
142 | + if self.IsSelected(i): | |
143 | + fnames.append(self.GetFilename(i)) | |
144 | + return fnames | |
145 | + | |
146 | + def open_button(self, event): | |
147 | + file_name = self.GetFilename(event.GetEventObject().GetId()) | |
148 | + print event.GetEventObject().GetId() | |
149 | + if file_name: | |
150 | + self.log_ctrl.AppendText(_("Starting ") + self.program + _(" for text") + " " + file_name + "...\n") | |
151 | + app = os.path.join("..", self.program, self.program+".exe") | |
152 | + app_bin = os.path.join("..", self.program, self.program) | |
153 | + if not os.path.exists(app): | |
154 | + app = app_bin | |
155 | + text = os.path.join("..", "teksty", file_name + self.extension) | |
156 | + try: | |
157 | + subprocess.Popen([app, text]) | |
158 | + except Exception as ex: | |
159 | + self.log_ctrl.AppendText(_("Error starting ") + self.program + ": " + unicode(str(ex), errors='replace') + "\n") | |
160 | + | |
... | ... |
gui/client.py
0 → 100644
1 | +++ a/gui/client.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import sys | |
3 | +import os | |
4 | +import shutil | |
5 | +import tempfile | |
6 | +import socket | |
7 | +import ssl | |
8 | +import pprint | |
9 | +from optparse import OptionParser | |
10 | +import traceback | |
11 | +import re | |
12 | + | |
13 | +import i18n | |
14 | +_ = i18n.language.ugettext | |
15 | + | |
16 | +from dfs.msg.stream import write_msg, read_msg, accept_msg | |
17 | +from dfs.msg.message import * | |
18 | +from dfs.utils import check_sum, send_contents, receive_contents | |
19 | + | |
20 | +from utils import normal_files, super_files | |
21 | + | |
22 | +def init_stream(host, port, cert, log=None): | |
23 | + log_msg(_("Connecting server..."), log) | |
24 | + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
25 | + stream = ssl.wrap_socket(s, ca_certs=cert, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1) | |
26 | + stream.connect((host, port)) | |
27 | + return stream | |
28 | + | |
29 | +def upload(stream, files, src_dir, file_exts, checkin=False, log=None): | |
30 | + """Checkin / Upload files to the server.""" | |
31 | + | |
32 | + def is_adj(path): | |
33 | + return False if len(os.path.dirname(path)) == 0 else True | |
34 | + | |
35 | + log_msg(_("Sending request..."), log) | |
36 | + if checkin: | |
37 | + write_msg(stream, CheckinRequest(len(files))) | |
38 | + else: | |
39 | + write_msg(stream, UploadRequest(len(files))) | |
40 | + done = [] | |
41 | + | |
42 | + failed = set() | |
43 | + for file_path in files: | |
44 | + log_msg(_("Uploading") + " " + file_path + "...", log) | |
45 | + | |
46 | + if not is_adj(file_path): | |
47 | + file_id = file_path | |
48 | + contents = read_contents(src_dir, file_path, file_exts) | |
49 | + else: | |
50 | + file_id, choice = os.path.split(file_path) | |
51 | + contents = read_contents(os.path.join(src_dir, file_id), choice, file_exts) | |
52 | + | |
53 | + write_msg(stream, Message(file_id)) | |
54 | + write_msg(stream, BoolMessage(is_adj(file_path))) | |
55 | + msg = read_msg(stream) | |
56 | + if type(msg) != OkMessage: | |
57 | + log_msg(_("Upload failed: server doesn't accept this file from you"), log) | |
58 | + failed.add(file_path) | |
59 | + continue | |
60 | + send_contents(stream, contents) | |
61 | + | |
62 | + write_msg(stream, ClientDone()) | |
63 | + accept_msg(stream, ServerDone) | |
64 | + | |
65 | + if not checkin: | |
66 | + log_msg(_("Done"), log) | |
67 | + return | |
68 | + | |
69 | + # When checkin command | |
70 | + for file_path in files: | |
71 | + if file_path in failed: | |
72 | + continue | |
73 | + if not is_adj(file_path): | |
74 | + log_msg(_("Deleting") + " " + file_path + "...", log) | |
75 | + for ext in file_exts: | |
76 | + os.remove(os.path.join(src_dir, file_path + ext)) | |
77 | + else: | |
78 | + file_id, ver = os.path.split(file_path) | |
79 | + log_msg(_("Deleting") + " " + file_id + "...", log) | |
80 | + shutil.rmtree(os.path.join(src_dir, file_id)) | |
81 | + | |
82 | + log_msg(_("Done"), log) | |
83 | + | |
84 | +def download(stream, n, dest_dir, tmp_dir, log=None): | |
85 | + """Download from server n files.""" | |
86 | + | |
87 | + log_msg(_("Sending request..."), log) | |
88 | + write_msg(stream, DownloadRequest(n)) | |
89 | + n = accept_msg(stream, NumMessage).get_number() | |
90 | + if n == 0: | |
91 | + log_msg(_("No files to download"), log) | |
92 | + | |
93 | + done = [] | |
94 | + for i in range(n): | |
95 | + file_id = accept_msg(stream, Message).get_contents() | |
96 | + log_msg(_("Downloading") + " " + file_id + "...", log) | |
97 | + contents = receive_contents(stream) | |
98 | + save_contents(contents, tmp_dir, file_id) | |
99 | + for ext in contents.keys(): | |
100 | + done.append(file_id + ext) | |
101 | + | |
102 | + write_msg(stream, ClientDone()) | |
103 | + accept_msg(stream, ServerDone) | |
104 | + for file_name in done: | |
105 | + from_path = os.path.join(tmp_dir, file_name) | |
106 | + to_path = os.path.join(dest_dir, file_name) | |
107 | + shutil.move(from_path, to_path) | |
108 | + log_msg(_("Done"), log) | |
109 | + | |
110 | +def download_prim(stream, n, dest_dir, tmp_dir, log=None): | |
111 | + """Download from server n pairs of files for adjudication.""" | |
112 | + | |
113 | + log_msg(_("Sending request..."), log) | |
114 | + write_msg(stream, DownloadPrimRequest(n)) | |
115 | + | |
116 | + msg = read_msg(stream) | |
117 | + if type(msg) != OkMessage: | |
118 | + log_msg(_("FAILED: you don't have adjudicator privileges"), log) | |
119 | + return | |
120 | + | |
121 | + n = accept_msg(stream, NumMessage).get_number() | |
122 | + anno_per_file = accept_msg(stream, NumMessage).get_number() | |
123 | + if n == 0: | |
124 | + log_msg(_("No files to download"), log) | |
125 | + | |
126 | + done = [] | |
127 | + for i in range(n): | |
128 | + file_id = accept_msg(stream, Message).get_contents() | |
129 | + log_msg(_("Downloading") + " " + file_id + "...", log) | |
130 | + | |
131 | + file_dir = os.path.join(tmp_dir, file_id) | |
132 | + os.mkdir(file_dir) | |
133 | + done.append(file_id) | |
134 | + | |
135 | + for j in range(anno_per_file): | |
136 | + save_contents(receive_contents(stream), file_dir, chr(ord('A') + j)) | |
137 | + | |
138 | + if accept_msg(stream, BoolMessage).get_boolean() == True: | |
139 | + if anno_per_file == 1: | |
140 | + save_contents(receive_contents(stream), file_dir, "A") | |
141 | + else: | |
142 | + save_contents(receive_contents(stream), file_dir, "Super") | |
143 | + | |
144 | + write_msg(stream, ClientDone()) | |
145 | + accept_msg(stream, ServerDone) | |
146 | + for fild_id in done: | |
147 | + from_path = os.path.join(tmp_dir, fild_id) | |
148 | + to_path = os.path.join(dest_dir, fild_id) | |
149 | + shutil.move(from_path, to_path) | |
150 | + log_msg(_("Done"), log) | |
151 | + | |
152 | +def check_stats(stream, log=None): | |
153 | + """Check annotatator stats.""" | |
154 | + | |
155 | + log_msg(_("Sending request..."), log) | |
156 | + write_msg(stream, StatsRequest()) | |
157 | + n = accept_msg(stream, NumMessage).get_number() | |
158 | + log_msg(_("Number of finished files: ") + str(n), log) | |
159 | + write_msg(stream, ClientDone()) | |
160 | + accept_msg(stream, ServerDone) | |
161 | + log_msg(_("Done"), log) | |
162 | + | |
163 | +def checkout(stream, dest_dir, tmp_dir, exts, log=None): | |
164 | + log_msg(_("Sending request..."), log) | |
165 | + write_msg(stream, CheckoutRequest()) | |
166 | + | |
167 | + done = [] | |
168 | + n = accept_msg(stream, NumMessage).get_number() | |
169 | + for i in range(n): | |
170 | + file_id = accept_msg(stream, Message).get_contents() | |
171 | + log_msg(_("Downloading") + " " + file_id + "...", log) | |
172 | + | |
173 | + contents = receive_contents(stream) | |
174 | + save_contents(contents, tmp_dir, file_id) | |
175 | + for ext in contents.keys(): | |
176 | + done.append(file_id + ext) | |
177 | + | |
178 | + n = accept_msg(stream, NumMessage).get_number() | |
179 | + anno_per_file = accept_msg(stream, NumMessage).get_number() | |
180 | + | |
181 | + for i in range(n): | |
182 | + file_id = accept_msg(stream, Message).get_contents() | |
183 | + log_msg(_("Downloading") + " " + file_id + "...", log) | |
184 | + | |
185 | + file_dir = os.path.join(tmp_dir, file_id) | |
186 | + os.mkdir(file_dir) | |
187 | + | |
188 | + for j in range(anno_per_file): | |
189 | + save_contents(receive_contents(stream), file_dir, chr(j + ord('A'))) | |
190 | + | |
191 | + if accept_msg(stream, BoolMessage).get_boolean() == True: | |
192 | + if anno_per_file == 1: | |
193 | + save_contents(receive_contents(stream), file_dir, "A") | |
194 | + else: | |
195 | + save_contents(receive_contents(stream), file_dir, "Super") | |
196 | + | |
197 | + done.append(file_id) | |
198 | + | |
199 | + write_msg(stream, ClientDone()) | |
200 | + accept_msg(stream, ServerDone) | |
201 | + | |
202 | + log_msg(_("Deleting local files..."), log) | |
203 | + try: | |
204 | + # normal texts | |
205 | + for f in normal_files(dest_dir, exts): | |
206 | + for ext in exts: | |
207 | + os.remove(os.path.join(dest_dir, f + ext)) | |
208 | + | |
209 | + # super annotated texts | |
210 | + dirs = set() | |
211 | + for f in super_files(dest_dir, exts): | |
212 | + dir = f.split(os.path.sep)[0] | |
213 | + dirs.add(dir) | |
214 | + for dir in dirs: | |
215 | + shutil.rmtree(os.path.join(dest_dir, dir)) | |
216 | + | |
217 | + except Exception as ex: | |
218 | + log_msg(_("Error occured:") + unicode(str(ex), errors='replace'), log) | |
219 | + | |
220 | + log_msg(_("Saving dowloaded files..."), log) | |
221 | + for path in done: | |
222 | + from_path = os.path.join(tmp_dir, path) | |
223 | + to_path = os.path.join(dest_dir, path) | |
224 | + shutil.move(from_path, to_path) | |
225 | + | |
226 | + log_msg(_("Done"), log) | |
227 | + | |
228 | +def return_files(stream, files, reason, src_dir, file_exts, log=None): | |
229 | + """Return files to the server.""" | |
230 | + | |
231 | + def is_adj(path): | |
232 | + return False if len(os.path.dirname(path)) == 0 else True | |
233 | + | |
234 | + log_msg(_("Sending request..."), log) | |
235 | + write_msg(stream, ReturnRequest(len(files))) | |
236 | + write_msg(stream, Message(reason)) | |
237 | + | |
238 | + done = [] | |
239 | + | |
240 | + failed = set() | |
241 | + for file_path in files: | |
242 | + log_msg(_("Uploading") + " " + file_path + "...", log) | |
243 | + | |
244 | + if not is_adj(file_path): | |
245 | + file_id = file_path | |
246 | + contents = read_contents(src_dir, file_path, file_exts) | |
247 | + else: | |
248 | + file_id, choice = os.path.split(file_path) | |
249 | + contents = read_contents(os.path.join(src_dir, file_id), choice, file_exts) | |
250 | + | |
251 | + write_msg(stream, Message(file_id)) | |
252 | + write_msg(stream, BoolMessage(is_adj(file_path))) | |
253 | + msg = read_msg(stream) | |
254 | + if type(msg) != OkMessage: | |
255 | + log_msg(_("Upload failed: server doesn't accept this file from you"), log) | |
256 | + failed.add(file_path) | |
257 | + continue | |
258 | + send_contents(stream, contents) | |
259 | + | |
260 | + write_msg(stream, ClientDone()) | |
261 | + accept_msg(stream, ServerDone) | |
262 | + | |
263 | + for file_path in files: | |
264 | + if file_path in failed: | |
265 | + continue | |
266 | + if not is_adj(file_path): | |
267 | + log_msg(_("Deleting") + " " + file_path + "...", log) | |
268 | + for ext in file_exts: | |
269 | + os.remove(os.path.join(src_dir, file_path + ext)) | |
270 | + else: | |
271 | + file_id, ver = os.path.split(file_path) | |
272 | + log_msg(_("Deleting") + " " + file_id + "...", log) | |
273 | + shutil.rmtree(os.path.join(src_dir, file_id)) | |
274 | + | |
275 | + log_msg(_("Done"), log) | |
276 | + | |
277 | +def save_contents(contents, dest_dir, file_id): | |
278 | + for ext, data in contents.iteritems(): | |
279 | + dest_path = os.path.join(dest_dir, file_id + ext) | |
280 | + with open(dest_path, "w") as dest: | |
281 | + dest.write(data) | |
282 | + | |
283 | +def read_contents(src_dir, file_id, file_exts): | |
284 | + result = {} | |
285 | + regex = re.compile("%s(.*)$" % file_id) | |
286 | + for entry in os.listdir(src_dir): | |
287 | + match = regex.match(entry) | |
288 | + if not match: | |
289 | + continue | |
290 | + with open(os.path.join(src_dir, entry)) as src: | |
291 | + ext = match.group(1) | |
292 | + if ext in file_exts: | |
293 | + result[ext] = src.read() | |
294 | + else: | |
295 | + print "Skipping file " + entry | |
296 | + return result | |
297 | + | |
298 | +def run_auth(stream, login, passwd, log=None): | |
299 | + log_msg(_("Authentication..."), log) | |
300 | + write_msg(stream, Message(login)) | |
301 | + write_msg(stream, Message(passwd)) | |
302 | + msg = read_msg(stream) | |
303 | + if type(msg) != OkMessage: | |
304 | + log_msg(_("FAILED: incorrect login or password"), log) | |
305 | + return False | |
306 | + return True | |
307 | + | |
308 | +def run_upload(login, passwd, host, port, cert, files, src_dir, file_exts, checkin=False, log=None): | |
309 | + result = True | |
310 | + try: | |
311 | + stream = init_stream(host, port, cert, log=log) | |
312 | + try: | |
313 | + if run_auth(stream, login, passwd, log=log): | |
314 | + upload(stream, files, src_dir, file_exts, checkin=checkin, log=log) | |
315 | + finally: | |
316 | + stream.close() | |
317 | + except: | |
318 | + log_msg(_("FAILED: unable to connect to server"), log) | |
319 | + print traceback.format_exc() | |
320 | + result = False | |
321 | + if log: | |
322 | + log.put(None) | |
323 | + return result | |
324 | + | |
325 | +def run_download(login, passwd, host, port, cert, n, dest_dir, log=None): | |
326 | + result = True | |
327 | + try: | |
328 | + stream = init_stream(host, port, cert, log=log) | |
329 | + try: | |
330 | + tmp_dir = tempfile.mkdtemp() | |
331 | + try: | |
332 | + if run_auth(stream, login, passwd, log=log): | |
333 | + download(stream, n, dest_dir, tmp_dir, log=log) | |
334 | + finally: | |
335 | + shutil.rmtree(tmp_dir) | |
336 | + finally: | |
337 | + stream.close() | |
338 | + except: | |
339 | + log_msg(_("FAILED: unable to connect to server"), log) | |
340 | + print traceback.format_exc() | |
341 | + result = False | |
342 | + if log: | |
343 | + log.put(None) | |
344 | + return result | |
345 | + | |
346 | +def run_check_stats(login, passwd, host, port, cert, log=None): | |
347 | + result = True | |
348 | + try: | |
349 | + stream = init_stream(host, port, cert, log=log) | |
350 | + try: | |
351 | + if run_auth(stream, login, passwd, log=log): | |
352 | + check_stats(stream, log=log) | |
353 | + finally: | |
354 | + stream.close() | |
355 | + except: | |
356 | + log_msg(_("FAILED: unable to connect to server"), log) | |
357 | + print traceback.format_exc() | |
358 | + result = False | |
359 | + if log: | |
360 | + log.put(None) | |
361 | + return result | |
362 | + | |
363 | +def run_download_prim(login, passwd, host, port, cert, n, dest_dir, log=None): | |
364 | + result = True | |
365 | + try: | |
366 | + stream = init_stream(host, port, cert, log=log) | |
367 | + try: | |
368 | + tmp_dir = tempfile.mkdtemp() | |
369 | + try: | |
370 | + if run_auth(stream, login, passwd, log=log): | |
371 | + download_prim(stream, n, dest_dir, tmp_dir, log=log) | |
372 | + finally: | |
373 | + shutil.rmtree(tmp_dir) | |
374 | + finally: | |
375 | + stream.close() | |
376 | + except: | |
377 | + log_msg(_("FAILED: unable to connect to server"), log) | |
378 | + print traceback.format_exc() | |
379 | + result = False | |
380 | + if log: | |
381 | + log.put(None) | |
382 | + return result | |
383 | + | |
384 | +def run_checkout(login, passwd, host, port, cert, dest_dir, exts, log=None): | |
385 | + result = True | |
386 | + try: | |
387 | + stream = init_stream(host, port, cert, log=log) | |
388 | + try: | |
389 | + tmp_dir = tempfile.mkdtemp() | |
390 | + try: | |
391 | + if run_auth(stream, login, passwd, log=log): | |
392 | + checkout(stream, dest_dir, tmp_dir, exts, log=log) | |
393 | + finally: | |
394 | + shutil.rmtree(tmp_dir) | |
395 | + finally: | |
396 | + stream.close() | |
397 | + except: | |
398 | + log_msg(_("FAILED: unable to connect to server"), log) | |
399 | + print traceback.format_exc() | |
400 | + result = False | |
401 | + if log: | |
402 | + log.put(None) | |
403 | + return result | |
404 | + | |
405 | +def run_return(login, passwd, host, port, cert, files, reason, src_dir, file_exts, log=None): | |
406 | + result = True | |
407 | + try: | |
408 | + stream = init_stream(host, port, cert, log=log) | |
409 | + try: | |
410 | + if run_auth(stream, login, passwd, log=log): | |
411 | + return_files(stream, files, reason, src_dir, file_exts, log) | |
412 | + finally: | |
413 | + stream.close() | |
414 | + except: | |
415 | + log_msg(_("FAILED: unable to connect to server"), log) | |
416 | + print traceback.format_exc() | |
417 | + result = False | |
418 | + if log: | |
419 | + log.put(None) | |
420 | + return result | |
421 | + | |
422 | +def log_msg(msg, log, new_line=True): | |
423 | + if new_line: | |
424 | + msg = msg + "\n" | |
425 | + sys.stdout.write(msg.encode('utf-8')) | |
426 | + if log: | |
427 | + log.put(msg) | |
428 | + | |
429 | +if __name__ == "__main__": | |
430 | + | |
431 | + optparser = OptionParser(usage="""usage: %prog [options] CMD CMD-ARGS | |
432 | + | |
433 | + Command line client for files distribution system.""") | |
434 | + | |
435 | + optparser.add_option("--login", dest="login") | |
436 | + optparser.add_option("--passwd", dest="passwd") | |
437 | + optparser.add_option("--home", metavar="DIR", dest="home", | |
438 | + help="Directory with users files.") | |
439 | + | |
440 | + (options, args) = optparser.parse_args() | |
441 | + | |
442 | + if options.login == None: | |
443 | + optparser.print_help() | |
444 | + print "\n--login option is mandatory" | |
445 | + sys.exit(0) | |
446 | + if options.passwd == None: | |
447 | + optparser.print_help() | |
448 | + print "\n--passwd option is mandatory" | |
449 | + sys.exit(0) | |
450 | + if options.home == None: | |
451 | + optparser.print_help() | |
452 | + print "\n--home option is mandatory" | |
453 | + sys.exit(0) | |
454 | + | |
455 | + commands = ["upload", "download", "download2"] | |
456 | + if len(args) < 1 or args[0] not in commands: | |
457 | + optparser.print_help() | |
458 | + print "\nCMD should be one of the following:" | |
459 | + print commands | |
460 | + sys.exit(0) | |
461 | + | |
462 | + cmd = args[0] | |
463 | + if cmd == "upload": | |
464 | + run_upload(options.login, options.passwd, args[1:], options.home) | |
465 | + elif cmd == "download": | |
466 | + run_download(options.login, options.passwd, | |
467 | + int(args[1]), options.home) | |
468 | + elif cmd == "download2": | |
469 | + run_download_prim(options.login, options.passwd, | |
470 | + int(args[1]), options.home) | |
... | ... |
gui/const.py
0 → 100644
1 | +++ a/gui/const.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import os | |
3 | + | |
4 | +from dfs.config import Config | |
5 | + | |
6 | +# Global configuration file | |
7 | +global_config_name = "data/global.cfg" | |
8 | +_cfg = Config(global_config_name) | |
9 | + | |
10 | +# File with list of directories | |
11 | +dir_cache_name = _cfg["general.dir_cache"] | |
12 | + | |
13 | +# File with directory configuration | |
14 | +config_name = _cfg["general.config"] | |
15 | +# File with file names marked as ready | |
16 | +ready_name = _cfg["general.ready"] | |
17 | +# File with files footprints | |
18 | +footprints_name = _cfg["general.footprints"] | |
19 | +# Default file extensions | |
20 | +default_exts = _cfg["general.default_exts"].split() | |
21 | +# Language (pl or en) | |
22 | +lang = _cfg["general.lang"] | |
23 | +# option to hide dir menu if precofigured | |
24 | +hide_dir_menu = _cfg["general.hide_dir_menu"] | |
25 | + | |
26 | +program = _cfg["general.program"] | |
27 | + | |
28 | +extension = _cfg["general.extension"] | |
29 | + | |
30 | +conn_host = _cfg["connection.host"] | |
31 | +conn_port = int(_cfg["connection.port"]) | |
32 | +conn_cert = _cfg["connection.cert"] | |
33 | + | |
34 | +# Path to directory-specific configuration files | |
35 | +def path(p, name): | |
36 | + return os.path.join(p, name) | |
... | ... |
gui/data/.dir.lst
0 → 100644
1 | +++ a/gui/data/.dir.lst | |
1 | +/home/me2/workspace/dist-system/data/foldery_anotatorow/ann1 | |
2 | +/home/me2/workspace/dist-system/data/foldery_anotatorow | |
3 | +/home/me2/workspace/core/dist-system/data/annotators_folders/ann2 | |
4 | +/home/me2/workspace/core/dist-system/data/annotators_folders/ann1 | |
5 | +/home/me2/workspace/core/dist-system/data/annotators_folders/adj1 | |
... | ... |
gui/data/cert.pem
0 → 100644
1 | +++ a/gui/data/cert.pem | |
1 | +-----BEGIN CERTIFICATE----- | |
2 | +MIICkDCCAfmgAwIBAgIJAJPcESSEV86nMA0GCSqGSIb3DQEBBQUAMGExCzAJBgNV | |
3 | +BAYTAlBMMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdD | |
4 | +b21wYW55MSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlQGV4YW1wbGUuY29tMB4XDTEz | |
5 | +MDEyMzEyMjIwN1oXDTE0MDEyMzEyMjIwN1owYTELMAkGA1UEBhMCUEwxDTALBgNV | |
6 | +BAgMBFRlc3QxDTALBgNVBAcMBENpdHkxEDAOBgNVBAoMB0NvbXBhbnkxIjAgBgkq | |
7 | +hkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD | |
8 | +gY0AMIGJAoGBAKUG9QHjUiCSjcvFQl1OqUgZ8c4Vzm7m7Ua7D33YDnGo3QGuhPML | |
9 | +0JB+JPBG++MrPdQqMV8eeXe1dFlrm6SHnrEZIhwZg8iDnPhFKNjHiAkx0PCQdI93 | |
10 | +rAe4EcszkgbQCXXLGjQJNDikwGZ6ciylJ7C2XewWTlWjv7/LqMwhQotLAgMBAAGj | |
11 | +UDBOMB0GA1UdDgQWBBRLRvOFIo9LIeiT2ohQalEzy687ijAfBgNVHSMEGDAWgBRL | |
12 | +RvOFIo9LIeiT2ohQalEzy687ijAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA | |
13 | +A4GBAD1odtgH1kZ4a0TEbwjK2PaHTYyofkkgeXsGGnG3eMkwKQ7PHJarBl7GtxP5 | |
14 | +Ls91k/KvBuNqCM/lPmM94USDSXrKGe0Zzc9WZbDG9SXO23gGOwPl+Tg9v1ocRHd2 | |
15 | +dYYIgZM8/tLcsHyNNMd7c4BOMD4rzkjgbThFFMl/2NSkvrO7 | |
16 | +-----END CERTIFICATE----- | |
... | ... |
gui/data/global.cfg
0 → 100644
1 | +++ a/gui/data/global.cfg | |
1 | +[general] | |
2 | + | |
3 | +config = .dir.cfg | |
4 | +ready = .ready.lst | |
5 | +footprints = .footprints.lst | |
6 | +default_exts = .mmax _mentions.xml _words.xml | |
7 | +lang = pl | |
8 | +hide_dir_menu = no | |
9 | +program = mmax | |
10 | +extension = .mmax | |
11 | + | |
12 | +{paths} | |
13 | +dir_cache = .dir.lst | |
14 | + | |
15 | +[connection] | |
16 | + | |
17 | +host = localhost | |
18 | +port = 2225 | |
19 | + | |
20 | +{paths} | |
21 | +cert = cert.pem | |
... | ... |
gui/dfs.wxg
0 → 100644
1 | +++ a/gui/dfs.wxg | |
1 | +<?xml version="1.0"?> | |
2 | +<!-- generated by wxGlade 0.6.3 on Fri Sep 30 18:28:38 2011 --> | |
3 | + | |
4 | +<application path="/home/kuba/Pulpit/praca/nkjp/dist-system/gui" name="manager" class="FileManager" option="1" language="python" top_window="main_frame" encoding="UTF-8" use_gettext="0" overwrite="0" use_new_namespace="1" for_version="2.8" is_template="0"> | |
5 | + <object class="MainFrame" name="main_frame" base="EditFrame"> | |
6 | + <style>wxDEFAULT_FRAME_STYLE</style> | |
7 | + <title>--</title> | |
8 | + <menubar>1</menubar> | |
9 | + <centered>1</centered> | |
10 | + <focused>1</focused> | |
11 | + <size>663, 556</size> | |
12 | + <object class="wxMenuBar" name="frame_1_menubar" base="EditMenuBar"> | |
13 | + <menus> | |
14 | + <menu name="dir_menu" label="Directory"> | |
15 | + <item> | |
16 | + <label>Open</label> | |
17 | + <id>5</id> | |
18 | + <name>dir_menu_open</name> | |
19 | + <help_str>Open new directory</help_str> | |
20 | + <handler>open_directory</handler> | |
21 | + </item> | |
22 | + <item> | |
23 | + <label>Reconfigure</label> | |
24 | + <id>6</id> | |
25 | + <help_str>Reconfigure directory</help_str> | |
26 | + <handler>reconf_directory</handler> | |
27 | + </item> | |
28 | + <item> | |
29 | + <label>---</label> | |
30 | + <id>---</id> | |
31 | + <name>---</name> | |
32 | + </item> | |
33 | + </menu> | |
34 | + <menu name="" label="Files"> | |
35 | + <item> | |
36 | + <label>Upload Selected</label> | |
37 | + <id>1</id> | |
38 | + <help_str>Upload annotated or adjudicated files</help_str> | |
39 | + <handler>upload</handler> | |
40 | + </item> | |
41 | + <item> | |
42 | + <label>Checkin Selected</label> | |
43 | + <id>8</id> | |
44 | + <help_str>Upload annotated or adjudicated; files will be deleted from local disk and you will not be able to modify them again</help_str> | |
45 | + <handler>checkin</handler> | |
46 | + </item> | |
47 | + <item> | |
48 | + <label>Download (annotation)</label> | |
49 | + <id>2</id> | |
50 | + <help_str>Download files for annotation</help_str> | |
51 | + <handler>download</handler> | |
52 | + </item> | |
53 | + <item> | |
54 | + <label>Download (adjudication)</label> | |
55 | + <id>3</id> | |
56 | + <help_str>Download files for adjudication</help_str> | |
57 | + <handler>download_prim</handler> | |
58 | + </item> | |
59 | + <item> | |
60 | + <label>Checkout</label> | |
61 | + <id>7</id> | |
62 | + <help_str>Checkout files from server to your local, empty directory</help_str> | |
63 | + <handler>checkout</handler> | |
64 | + </item> | |
65 | + </menu> | |
66 | + <menu name="" label="Select"> | |
67 | + <item> | |
68 | + <label>Ready</label> | |
69 | + <id>4</id> | |
70 | + <help_str>Select items which are marked for upload</help_str> | |
71 | + <handler>select_ready</handler> | |
72 | + </item> | |
73 | + <item> | |
74 | + <label>Modified</label> | |
75 | + <id>9</id> | |
76 | + <help_str>Select items which are modified</help_str> | |
77 | + <handler>select_modified</handler> | |
78 | + </item> | |
79 | + </menu> | |
80 | + <menu name="" label="Help"> | |
81 | + </menu> | |
82 | + </menus> | |
83 | + </object> | |
84 | + <object class="wxBoxSizer" name="sizer_6" base="EditBoxSizer"> | |
85 | + <orient>wxHORIZONTAL</orient> | |
86 | + <object class="sizeritem"> | |
87 | + <flag>wxEXPAND</flag> | |
88 | + <border>0</border> | |
89 | + <option>1</option> | |
90 | + <object class="wxSplitterWindow" name="window_1" base="EditSplitterWindow"> | |
91 | + <style>wxSP_3D|wxSP_BORDER</style> | |
92 | + <orientation>wxSPLIT_HORIZONTAL</orientation> | |
93 | + <sash_pos>-100</sash_pos> | |
94 | + <window_2>window_1_pane_2</window_2> | |
95 | + <window_1>window_1_pane_1</window_1> | |
96 | + <object class="wxPanel" name="window_1_pane_1" base="EditPanel"> | |
97 | + <style>wxTAB_TRAVERSAL</style> | |
98 | + <object class="wxBoxSizer" name="sizer_1" base="EditBoxSizer"> | |
99 | + <orient>wxVERTICAL</orient> | |
100 | + <object class="sizeritem"> | |
101 | + <flag>wxEXPAND</flag> | |
102 | + <border>0</border> | |
103 | + <option>1</option> | |
104 | + <object class="wxNotebook" name="notebook_1" base="EditNotebook"> | |
105 | + <style>0</style> | |
106 | + <tabs> | |
107 | + <tab window="notebook_1_pane_1">annotation</tab> | |
108 | + <tab window="notebook_1_pane_2">adjudication</tab> | |
109 | + </tabs> | |
110 | + <object class="wxPanel" name="notebook_1_pane_1" base="EditPanel"> | |
111 | + <style>wxTAB_TRAVERSAL</style> | |
112 | + <object class="wxBoxSizer" name="sizer_2" base="EditBoxSizer"> | |
113 | + <orient>wxVERTICAL</orient> | |
114 | + <object class="sizeritem"> | |
115 | + <flag>wxEXPAND</flag> | |
116 | + <border>0</border> | |
117 | + <option>1</option> | |
118 | + <object class="wxListCtrl" name="files_list" base="EditListCtrl"> | |
119 | + <style>wxLC_REPORT|wxSIMPLE_BORDER</style> | |
120 | + <events> | |
121 | + <handler event="EVT_LIST_ITEM_ACTIVATED">activate_item</handler> | |
122 | + </events> | |
123 | + </object> | |
124 | + </object> | |
125 | + </object> | |
126 | + </object> | |
127 | + <object class="wxPanel" name="notebook_1_pane_2" base="EditPanel"> | |
128 | + <style>wxTAB_TRAVERSAL</style> | |
129 | + <object class="wxBoxSizer" name="sizer_3" base="EditBoxSizer"> | |
130 | + <orient>wxVERTICAL</orient> | |
131 | + <object class="sizeritem"> | |
132 | + <flag>wxEXPAND</flag> | |
133 | + <border>0</border> | |
134 | + <option>1</option> | |
135 | + <object class="wxListCtrl" name="super_files_list" base="EditListCtrl"> | |
136 | + <style>wxLC_REPORT|wxSIMPLE_BORDER</style> | |
137 | + <events> | |
138 | + <handler event="EVT_LIST_ITEM_ACTIVATED">activate_super_item</handler> | |
139 | + </events> | |
140 | + </object> | |
141 | + </object> | |
142 | + </object> | |
143 | + </object> | |
144 | + </object> | |
145 | + </object> | |
146 | + </object> | |
147 | + </object> | |
148 | + <object class="wxPanel" name="window_1_pane_2" base="EditPanel"> | |
149 | + <style>wxTAB_TRAVERSAL</style> | |
150 | + <object class="wxBoxSizer" name="sizer_7" base="EditBoxSizer"> | |
151 | + <orient>wxVERTICAL</orient> | |
152 | + <object class="sizeritem"> | |
153 | + <flag>wxEXPAND</flag> | |
154 | + <border>0</border> | |
155 | + <option>1</option> | |
156 | + <object class="wxTextCtrl" name="log_ctrl" base="EditTextCtrl"> | |
157 | + <style>wxTE_MULTILINE|wxTE_READONLY</style> | |
158 | + </object> | |
159 | + </object> | |
160 | + </object> | |
161 | + </object> | |
162 | + </object> | |
163 | + </object> | |
164 | + </object> | |
165 | + </object> | |
166 | + <object class="LoginDialog" name="login_dialog" base="EditDialog"> | |
167 | + <style>wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER|wxTHICK_FRAME</style> | |
168 | + <title>login</title> | |
169 | + <object class="wxBoxSizer" name="sizer_4" base="EditBoxSizer"> | |
170 | + <orient>wxVERTICAL</orient> | |
171 | + <object class="sizeritem"> | |
172 | + <border>0</border> | |
173 | + <option>0</option> | |
174 | + <object class="wxGridSizer" name="grid_sizer_1" base="EditGridSizer"> | |
175 | + <hgap>0</hgap> | |
176 | + <rows>2</rows> | |
177 | + <cols>2</cols> | |
178 | + <vgap>0</vgap> | |
179 | + <object class="sizeritem"> | |
180 | + <flag>wxALIGN_CENTER_VERTICAL</flag> | |
181 | + <border>0</border> | |
182 | + <option>0</option> | |
183 | + <object class="wxStaticText" name="label_1" base="EditStaticText"> | |
184 | + <attribute>1</attribute> | |
185 | + <label>login: </label> | |
186 | + </object> | |
187 | + </object> | |
188 | + <object class="sizeritem"> | |
189 | + <border>0</border> | |
190 | + <option>0</option> | |
191 | + <object class="wxTextCtrl" name="login_ctrl" base="EditTextCtrl"> | |
192 | + </object> | |
193 | + </object> | |
194 | + <object class="sizeritem"> | |
195 | + <flag>wxALIGN_CENTER_VERTICAL</flag> | |
196 | + <border>0</border> | |
197 | + <option>0</option> | |
198 | + <object class="wxStaticText" name="label_3" base="EditStaticText"> | |
199 | + <attribute>1</attribute> | |
200 | + <label>password: </label> | |
201 | + </object> | |
202 | + </object> | |
203 | + <object class="sizeritem"> | |
204 | + <border>0</border> | |
205 | + <option>0</option> | |
206 | + <object class="wxTextCtrl" name="passwd_ctrl" base="EditTextCtrl"> | |
207 | + <style>wxTE_PASSWORD</style> | |
208 | + </object> | |
209 | + </object> | |
210 | + </object> | |
211 | + </object> | |
212 | + <object class="sizeritem"> | |
213 | + <border>0</border> | |
214 | + <option>0</option> | |
215 | + <object class="wxCheckBox" name="save_passwd" base="EditCheckBox"> | |
216 | + <label>save password</label> | |
217 | + </object> | |
218 | + </object> | |
219 | + <object class="sizeritem"> | |
220 | + <flag>wxALIGN_CENTER_HORIZONTAL</flag> | |
221 | + <border>0</border> | |
222 | + <option>0</option> | |
223 | + <object class="wxButton" name="button_1" base="EditButton"> | |
224 | + <label>OK</label> | |
225 | + <id>wx.ID_OK</id> | |
226 | + <events> | |
227 | + <handler event="EVT_BUTTON">on_ok</handler> | |
228 | + </events> | |
229 | + <size>85, 32</size> | |
230 | + </object> | |
231 | + </object> | |
232 | + </object> | |
233 | + </object> | |
234 | + <object class="DownloadNumberDialog" name="download_number" base="EditDialog"> | |
235 | + <style>wxDEFAULT_DIALOG_STYLE</style> | |
236 | + <title>checkout</title> | |
237 | + <centered>1</centered> | |
238 | + <object class="wxGridSizer" name="grid_sizer_2" base="EditGridSizer"> | |
239 | + <hgap>0</hgap> | |
240 | + <rows>2</rows> | |
241 | + <cols>2</cols> | |
242 | + <vgap>0</vgap> | |
243 | + <object class="sizeritem"> | |
244 | + <flag>wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL</flag> | |
245 | + <border>0</border> | |
246 | + <option>0</option> | |
247 | + <object class="wxStaticText" name="label_4" base="EditStaticText"> | |
248 | + <attribute>1</attribute> | |
249 | + <tooltip>Number of files to download</tooltip> | |
250 | + <label>download:</label> | |
251 | + </object> | |
252 | + </object> | |
253 | + <object class="sizeritem"> | |
254 | + <border>0</border> | |
255 | + <option>0</option> | |
256 | + <object class="wxSpinCtrl" name="checkout_number" base="EditSpinCtrl"> | |
257 | + <range>1, 300</range> | |
258 | + <value>1</value> | |
259 | + </object> | |
260 | + </object> | |
261 | + <object class="sizeritem"> | |
262 | + <flag>wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL</flag> | |
263 | + <border>0</border> | |
264 | + <option>0</option> | |
265 | + <object class="wxButton" name="button_4" base="EditButton"> | |
266 | + <label>OK</label> | |
267 | + <id>wx.ID_YES</id> | |
268 | + <events> | |
269 | + <handler event="EVT_BUTTON">on_ok</handler> | |
270 | + </events> | |
271 | + </object> | |
272 | + </object> | |
273 | + <object class="sizeritem"> | |
274 | + <flag>wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL</flag> | |
275 | + <border>0</border> | |
276 | + <option>0</option> | |
277 | + <object class="wxButton" name="button_5" base="EditButton"> | |
278 | + <label>Cancel</label> | |
279 | + <id>wx.ID_NO</id> | |
280 | + <events> | |
281 | + <handler event="EVT_BUTTON">on_cancel</handler> | |
282 | + </events> | |
283 | + </object> | |
284 | + </object> | |
285 | + </object> | |
286 | + </object> | |
287 | + <object class="ConfigDialog" name="config_dialog" base="EditDialog"> | |
288 | + <style>wxDEFAULT_DIALOG_STYLE</style> | |
289 | + <title>Directory configuration</title> | |
290 | + <object class="wxBoxSizer" name="sizer_5" base="EditBoxSizer"> | |
291 | + <orient>wxVERTICAL</orient> | |
292 | + <object class="sizeritem"> | |
293 | + <border>0</border> | |
294 | + <option>0</option> | |
295 | + <object class="wxGridSizer" name="grid_sizer_4" base="EditGridSizer"> | |
296 | + <hgap>0</hgap> | |
297 | + <rows>3</rows> | |
298 | + <cols>2</cols> | |
299 | + <vgap>0</vgap> | |
300 | + <object class="sizeritem"> | |
301 | + <flag>wxALIGN_CENTER_VERTICAL</flag> | |
302 | + <border>0</border> | |
303 | + <option>0</option> | |
304 | + <object class="wxStaticText" name="label_2" base="EditStaticText"> | |
305 | + <attribute>1</attribute> | |
306 | + <tooltip>Login supplied by system administrator</tooltip> | |
307 | + <label>login:</label> | |
308 | + </object> | |
309 | + </object> | |
310 | + <object class="sizeritem"> | |
311 | + <flag>wxALIGN_CENTER_VERTICAL</flag> | |
312 | + <border>0</border> | |
313 | + <option>0</option> | |
314 | + <object class="wxTextCtrl" name="login_ctrl" base="EditTextCtrl"> | |
315 | + </object> | |
316 | + </object> | |
317 | + <object class="sizeritem"> | |
318 | + <flag>wxALIGN_CENTER_VERTICAL</flag> | |
319 | + <border>0</border> | |
320 | + <option>0</option> | |
321 | + <object class="wxStaticText" name="label_6" base="EditStaticText"> | |
322 | + <attribute>1</attribute> | |
323 | + <tooltip>Password supplied by system administrator</tooltip> | |
324 | + <label>password:</label> | |
325 | + </object> | |
326 | + </object> | |
327 | + <object class="sizeritem"> | |
328 | + <flag>wxALIGN_CENTER_VERTICAL</flag> | |
329 | + <border>0</border> | |
330 | + <option>0</option> | |
331 | + <object class="wxTextCtrl" name="passwd_ctrl" base="EditTextCtrl"> | |
332 | + <style>wxTE_PASSWORD</style> | |
333 | + </object> | |
334 | + </object> | |
335 | + <object class="sizeritem"> | |
336 | + <flag>wxALIGN_CENTER_VERTICAL</flag> | |
337 | + <border>0</border> | |
338 | + <option>0</option> | |
339 | + <object class="wxStaticText" name="label_5" base="EditStaticText"> | |
340 | + <attribute>1</attribute> | |
341 | + <tooltip>List of space separated file extensions</tooltip> | |
342 | + <label>extensions:</label> | |
343 | + </object> | |
344 | + </object> | |
345 | + <object class="sizeritem"> | |
346 | + <flag>wxALIGN_CENTER_VERTICAL</flag> | |
347 | + <border>0</border> | |
348 | + <option>0</option> | |
349 | + <object class="wxTextCtrl" name="exts_ctrl" base="EditTextCtrl"> | |
350 | + </object> | |
351 | + </object> | |
352 | + </object> | |
353 | + </object> | |
354 | + <object class="sizeritem"> | |
355 | + <flag>wxALIGN_CENTER_HORIZONTAL</flag> | |
356 | + <border>0</border> | |
357 | + <option>0</option> | |
358 | + <object class="wxButton" name="button_2" base="EditButton"> | |
359 | + <label>OK</label> | |
360 | + <id>wx.ID_OK</id> | |
361 | + <focused>1</focused> | |
362 | + <size>85, 29</size> | |
363 | + </object> | |
364 | + </object> | |
365 | + </object> | |
366 | + </object> | |
367 | +</application> | |
... | ... |
gui/dfs/__init__.py
0 → 100644
1 | +++ a/gui/dfs/__init__.py | |
... | ... |
gui/dfs/config.py
0 → 100644
1 | +++ a/gui/dfs/config.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import re | |
3 | +import os | |
4 | + | |
5 | +SECTION = re.compile("^\[(.*)\]$") | |
6 | +SUBSECTION = re.compile("^\{(.*)\}$") | |
7 | +ENTRY = re.compile("^([^=\s]+)\s*=\s*([^=]+)\s*$") | |
8 | + | |
9 | +def cut_comment(s): | |
10 | + return s.split("#")[0] | |
11 | + | |
12 | +class Config: | |
13 | + | |
14 | + def __init__(self, path): | |
15 | + section = None | |
16 | + subsection = None | |
17 | + self.d = {} | |
18 | + with open(path) as f: | |
19 | + for line in f: | |
20 | + line = cut_comment(line).strip() | |
21 | + | |
22 | + m = SECTION.search(line) | |
23 | + if m is not None: | |
24 | + section = m.group(1) | |
25 | + subsection = None | |
26 | + continue | |
27 | + | |
28 | + m = SUBSECTION.search(line) | |
29 | + if m is not None: | |
30 | + subsection = m.group(1) | |
31 | + continue | |
32 | + | |
33 | + m = ENTRY.search(line) | |
34 | + if m is not None: | |
35 | + key = m.group(1) | |
36 | + val = m.group(2) | |
37 | + if subsection == "paths": | |
38 | + # print path, val, os.path.join(path, val) | |
39 | + val = self.relative_path(val, path) | |
40 | + if section is not None: | |
41 | + self.d[section + "." + key] = val | |
42 | + else: | |
43 | + self.d[key] = val | |
44 | + | |
45 | + def relative_path(self, path, ref): | |
46 | + return os.path.join(os.path.dirname(ref), path) | |
47 | + | |
48 | + def __getitem__(self, key): | |
49 | + return self.d[key] | |
... | ... |
gui/dfs/database.py
0 → 100644
1 | +++ a/gui/dfs/database.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import time | |
3 | +from datetime import date, datetime | |
4 | +from collections import defaultdict | |
5 | + | |
6 | +from xml.etree.ElementTree import Element, ElementTree, tostring | |
7 | +import xml.etree.ElementTree as ET | |
8 | + | |
9 | +class Database: | |
10 | + | |
11 | + def __init__(self, db, anno_per_file, from_file=True): | |
12 | + if from_file: | |
13 | + self.db_name = db | |
14 | + self.tree = ET.parse(db) | |
15 | + self.root = self.tree.getroot() | |
16 | + else: | |
17 | + self.db_name = None | |
18 | + self.tree = None | |
19 | + self.root = ET.fromstring(db) | |
20 | + | |
21 | + self.anno_per_file = anno_per_file | |
22 | + self.make_index() | |
23 | + | |
24 | + def owned(self, ann_elem): | |
25 | + return not self.finished(ann_elem) and not self.returned(ann_elem) | |
26 | + | |
27 | + def finished(self, ann_elem): | |
28 | + return ann_elem.find("checkinDate") is not None | |
29 | + | |
30 | + def returned(self, ann_elem): | |
31 | + return ann_elem.find("return") is not None | |
32 | + | |
33 | + def get_reason(self, ann_elem): | |
34 | + reas = ann_elem.find("return").find("reason").text | |
35 | + if reas is None: | |
36 | + reas = "" | |
37 | + return reas | |
38 | + | |
39 | + def rejected(self, file_elem): | |
40 | + return file_elem.find("rejected") is not None | |
41 | + | |
42 | + def fixed(self, ann_elem): | |
43 | + ret = ann_elem.find("return") | |
44 | + if ret is not None and ret.find("fixed") is not None: | |
45 | + return True | |
46 | + return False | |
47 | + | |
48 | + def ann_elem_for_user(self, file_elem, user): | |
49 | + idx = 0 | |
50 | + for ann_elem in file_elem.findall("ann"): | |
51 | + if ann_elem.find("annName").text == user: | |
52 | + return ann_elem, idx | |
53 | + idx = idx + 1 | |
54 | + return None, None | |
55 | + | |
56 | + def sann_elem_for_user(self, file_elem, user): | |
57 | + for ann_elem in file_elem.findall("s_ann"): | |
58 | + if ann_elem.find("annName").text == user: | |
59 | + return ann_elem | |
60 | + return None | |
61 | + | |
62 | + def fixed_for_user(self, file_elem, user, is_adj): | |
63 | + if is_adj: | |
64 | + ann = self.sann_elem_for_user(file_elem, user) | |
65 | + else: | |
66 | + ann, idx = self.ann_elem_for_user(file_elem, user) | |
67 | + if ann is not None: | |
68 | + return self.fixed(ann) | |
69 | + return False | |
70 | + | |
71 | + def make_index(self): | |
72 | + self.file_index = {} | |
73 | + self.ann_owned_index = defaultdict(list) | |
74 | + self.adj_owned_index = defaultdict(list) | |
75 | + | |
76 | + for file_elem in self.root: | |
77 | + name_elem = file_elem.find("name") | |
78 | + if name_elem == None: | |
79 | + raise Exception("No name assigned to a file element !") | |
80 | + self.file_index[name_elem.text] = file_elem | |
81 | + | |
82 | + if self.rejected(file_elem): | |
83 | + continue | |
84 | + | |
85 | + for ann_elem in file_elem.findall("ann"): | |
86 | + if self.owned(ann_elem): | |
87 | + ann_name = ann_elem.find("annName").text | |
88 | + self.ann_owned_index[ann_name].append(file_elem) | |
89 | + | |
90 | + for ann_elem in file_elem.findall("s_ann"): | |
91 | + if self.owned(ann_elem): | |
92 | + ann_name = ann_elem.find("annName").text | |
93 | + self.adj_owned_index[ann_name].append(file_elem) | |
94 | + | |
95 | + def fix(self, ann_elem): | |
96 | + if not self.returned(ann_elem): | |
97 | + raise Exception("File not returned in database!") | |
98 | + if self.fixed(ann_elem): | |
99 | + raise Exception("File already fixed in database!") | |
100 | + | |
101 | + ret = ann_elem.find("return") | |
102 | + fixed = ET.SubElement(ret, "fixed") | |
103 | + | |
104 | + def add(self, file_name): | |
105 | + if self.file_index.has_key(file_name): | |
106 | + raise Exception("File name already in database !") | |
107 | + file_elem = ET.SubElement(self.root, "file") | |
108 | + name_elem = ET.SubElement(file_elem, "name") | |
109 | + name_elem.text = file_name | |
110 | + date_elem = ET.SubElement(file_elem, "addDate") | |
111 | + date_elem.text = time.strftime("%c") | |
112 | + self.file_index[file_name] = file_elem | |
113 | + | |
114 | + def remove(self, file_name): | |
115 | + if not self.file_index.has_key(file_name): | |
116 | + raise Exception("File name not in database !") | |
117 | + file_elem = self.file_index[file_name] | |
118 | + self.root.remove(file_elem) | |
119 | + self.make_index() | |
120 | + | |
121 | + def reject(self, file_name, reason): | |
122 | + if not self.file_index.has_key(file_name): | |
123 | + raise Exception("File name not in database !") | |
124 | + file_elem = self.file_index[file_name] | |
125 | + if self.rejected(file_elem): | |
126 | + raise Exception("File already rejected !") | |
127 | + rej_elem = ET.SubElement(file_elem, "rejected") | |
128 | + rej_elem.text = reason | |
129 | + self.make_index() | |
130 | + | |
131 | + # For prettyprint, used in 'save' method. | |
132 | + def indent(self, elem, level=0): | |
133 | + i = "\n" + level * " " | |
134 | + if len(elem): | |
135 | + if not elem.text or not elem.text.strip(): | |
136 | + elem.text = i + " " | |
137 | + if not elem.tail or not elem.tail.strip(): | |
138 | + elem.tail = i | |
139 | + for elem in elem: | |
140 | + self.indent(elem, level + 1) | |
141 | + if not elem.tail or not elem.tail.strip(): | |
142 | + elem.tail = i | |
143 | + else: | |
144 | + if level and (not elem.tail or not elem.tail.strip()): | |
145 | + elem.tail = i | |
146 | + | |
147 | + def save(self): | |
148 | + if not self.db_name: | |
149 | + raise Exception("XML not read from file !") | |
150 | + | |
151 | + self.indent(self.root) | |
152 | + self.tree.write(self.db_name, encoding="utf-8") | |
153 | + | |
154 | + def upload_prevention(self, file_name, user, is_super): | |
155 | + if not self.file_index.has_key(file_name): | |
156 | + return "Filename " + file_name.encode('utf-8') + " not known by server." | |
157 | + file_elem = self.file_index[file_name] | |
158 | + if self.rejected(file_elem): | |
159 | + return "File rejected: " + file_name.encode('utf-8') | |
160 | + | |
161 | + if is_super: | |
162 | + ann_elem = file_elem.find("s_ann") | |
163 | + if ann_elem is None: | |
164 | + return "No superannotator assigned to file " + file_name.encode('utf-8') | |
165 | + | |
166 | + if user != ann_elem.find("annName").text: | |
167 | + return "Different superannotator assigned to file " + file_name.encode('utf-8') | |
168 | + | |
169 | + if ann_elem.find("checkinDate") is not None: | |
170 | + return "File " + file_name.encode('utf-8') + " already checked in!" | |
171 | + | |
172 | + return None | |
173 | + | |
174 | + else: | |
175 | + annotations = file_elem.findall("ann") | |
176 | + owners = map(lambda ann: ann.find("annName").text, annotations) | |
177 | + if not user in owners: | |
178 | + return "User " + user + " doesn't own the file " + file_name.encode('utf-8') | |
179 | + | |
180 | + i = owners.index(user) | |
181 | + ann_elem = annotations[i] | |
182 | + if ann_elem.find("checkinDate") is not None: | |
183 | + return "User " + user + " already checked in the file " + file_name.encode('utf-8') | |
184 | + | |
185 | + return None | |
186 | + | |
187 | + def upload_dest(self, file_name, user): | |
188 | + """Upload destination (annName element and ID -- 1 or 2) directory.""" | |
189 | + file_elem = self.file_index[file_name] | |
190 | + annotations = file_elem.findall("ann") | |
191 | + | |
192 | + owners = map(lambda ann: ann.find("annName").text, annotations) | |
193 | + exc = Exception("Cannot upload " + file_name.encode('utf-8') | |
194 | + + " file by " + user + " annotator !") | |
195 | + if not user in owners: | |
196 | + raise exc | |
197 | + | |
198 | + i = owners.index(user) | |
199 | + ann_elem = annotations[i] | |
200 | + if ann_elem.find("checkinDate") is not None: | |
201 | + raise exc | |
202 | + | |
203 | + return (ann_elem, i) | |
204 | + | |
205 | + def upload_id(self, file_name, user): | |
206 | + return self.upload_dest(file_name, user)[1] | |
207 | + | |
208 | + def return_file(self, file_name, user, reason): | |
209 | + (ann_elem, i) = self.upload_dest(file_name, user) | |
210 | + ret_elem = ET.SubElement(ann_elem, "return") | |
211 | + date_elem = ET.SubElement(ret_elem, "date") | |
212 | + date_elem.text = time.strftime("%c") | |
213 | + date_elem = ET.SubElement(ret_elem, "reason") | |
214 | + if reason is None: | |
215 | + reason = "" | |
216 | + date_elem.text = reason | |
217 | + return i | |
218 | + | |
219 | + def return_file_prim(self, file_name, user, reason): | |
220 | + file_elem = self.file_index[file_name] | |
221 | + ann_elem = file_elem.find("s_ann") | |
222 | + | |
223 | + if (user != ann_elem.find("annName").text or | |
224 | + ann_elem.find("checkinDate") is not None): | |
225 | + raise Exception("Cannot upload " + file_name.encode('utf-8') | |
226 | + + " file by " + user + " annotator !") | |
227 | + | |
228 | + ret_elem = ET.SubElement(ann_elem, "return") | |
229 | + date_elem = ET.SubElement(ret_elem, "date") | |
230 | + date_elem.text = time.strftime("%c") | |
231 | + date_elem = ET.SubElement(ret_elem, "reason") | |
232 | + if reason is None: | |
233 | + reason = "" | |
234 | + date_elem.text = reason | |
235 | + | |
236 | + def upload(self, file_name, user): | |
237 | + (ann_elem, i) = self.upload_dest(file_name, user) | |
238 | + date_elem = ET.SubElement(ann_elem, "checkinDate") | |
239 | + date_elem.text = time.strftime("%c") | |
240 | + return i | |
241 | + | |
242 | + def upload_prim(self, file_name, user): | |
243 | + file_elem = self.file_index[file_name] | |
244 | + ann_elem = file_elem.find("s_ann") | |
245 | + | |
246 | + if (user != ann_elem.find("annName").text or self.finished(ann_elem)): | |
247 | + raise Exception("Cannot upload " + file_name.encode('utf-8') | |
248 | + + " file by " + user + " annotator !") | |
249 | + | |
250 | + date_elem = ET.SubElement(ann_elem, "checkinDate") | |
251 | + date_elem.text = time.strftime("%c") | |
252 | + | |
253 | + def download(self, file_name, user): | |
254 | + file_elem = self.file_index[file_name] | |
255 | + | |
256 | + annotations = file_elem.findall("ann") | |
257 | + owners = map(lambda ann: ann.find("annName").text, annotations) | |
258 | + | |
259 | + if self.fixed_for_user(file_elem, user, False): | |
260 | + ann, idx = self.ann_elem_for_user(file_elem, user) | |
261 | + date = ann.find("return").find("date").text | |
262 | + ann.remove(ann.find("return")) | |
263 | + | |
264 | + date_elem = ET.SubElement(ann, "returnDate") | |
265 | + date_elem.text = date | |
266 | + | |
267 | + date_elem = ET.SubElement(ann, "checkoutDate") | |
268 | + date_elem.text = time.strftime("%c") | |
269 | + | |
270 | + return idx | |
271 | + | |
272 | + else: | |
273 | + if user in owners or len(owners) + 1 > self.anno_per_file: | |
274 | + raise Exception("Cannot set " + user + " annotator to '" | |
275 | + + file_name.encode('utf-8') + "' file !") | |
276 | + | |
277 | + ann_elem = ET.SubElement(file_elem, "ann") | |
278 | + ann_name_elem = ET.SubElement(ann_elem, "annName") | |
279 | + ann_name_elem.text = user | |
280 | + | |
281 | + date_elem = ET.SubElement(ann_elem, "checkoutDate") | |
282 | + date_elem.text = time.strftime("%c") | |
283 | + | |
284 | + return None | |
285 | + | |
286 | + def download_prim(self, file_name, user): | |
287 | + file_elem = self.file_index[file_name] | |
288 | + | |
289 | + if self.fixed_for_user(file_elem, user, True): | |
290 | + sann = self.sann_elem_for_user(file_elem, user) | |
291 | + date = sann.find("return").find("date").text | |
292 | + sann.remove(sann.find("return")) | |
293 | + | |
294 | + date_elem = ET.SubElement(sann, "returnDate") | |
295 | + date_elem.text = date | |
296 | + | |
297 | + date_elem = ET.SubElement(sann, "checkoutDate") | |
298 | + date_elem.text = time.strftime("%c") | |
299 | + | |
300 | + return True | |
301 | + | |
302 | + else: | |
303 | + if len(file_elem.findall("s_ann")) > 0: | |
304 | + raise Exception("Cannot set " + user + " adjudicator to '" | |
305 | + + file_name.encode('utf-8') + "' file !") | |
306 | + | |
307 | + ann_elem = ET.SubElement(file_elem, "s_ann") | |
308 | + ann_name_elem = ET.SubElement(ann_elem, "annName") | |
309 | + ann_name_elem.text = user | |
310 | + | |
311 | + date_elem = ET.SubElement(ann_elem, "checkoutDate") | |
312 | + date_elem.text = time.strftime("%c") | |
313 | + | |
314 | + return False | |
315 | + | |
316 | + def for_annotation(self, user): | |
317 | + priority = [] | |
318 | + result = [] | |
319 | + for file_elem in self.root: | |
320 | + if self.rejected(file_elem) or file_elem.find("s_ann") is not None: | |
321 | + continue | |
322 | + | |
323 | + anns = file_elem.findall("ann") | |
324 | + owners = map(lambda ann: ann.find("annName").text, anns) | |
325 | + if len(owners) < self.anno_per_file and user not in owners: | |
326 | + result.append(file_elem.find("name").text) | |
327 | + | |
328 | + if self.fixed_for_user(file_elem, user, False): | |
329 | + priority.append(file_elem.find("name").text) | |
330 | + | |
331 | + return priority, result | |
332 | + | |
333 | + def for_adjudication(self, user): | |
334 | + priority = [] | |
335 | + result = [] | |
336 | + for file_elem in self.root: | |
337 | + if self.rejected(file_elem): | |
338 | + continue | |
339 | + | |
340 | + anns = file_elem.findall("ann") | |
341 | + owners = map(lambda ann: ann.find("annName").text, anns) | |
342 | + checked = map(lambda ann: ann.find("checkinDate") != None, anns) | |
343 | + if (len(owners) == self.anno_per_file | |
344 | + and file_elem.find("s_ann") is None | |
345 | + and user not in owners | |
346 | + and all(checked)): | |
347 | + result.append(file_elem.find("name").text) | |
348 | + | |
349 | + if self.fixed_for_user(file_elem, user, True): | |
350 | + priority.append(file_elem.find("name").text) | |
351 | + | |
352 | + return priority, result | |
353 | + | |
354 | + def owns_normal(self, user): | |
355 | + return [ file_elem.find("name").text | |
356 | + for file_elem | |
357 | + in self.ann_owned_index[user] ] | |
358 | + | |
359 | + def owns_super(self, user): | |
360 | + return [ file_elem.find("name").text | |
361 | + for file_elem | |
362 | + in self.adj_owned_index[user] ] | |
363 | + | |
364 | + def owns(self, user): | |
365 | + return self.owns_normal(user) + self.owns_super(user) | |
366 | + | |
367 | + def finished_count(self, user): | |
368 | + n = 0 | |
369 | + for file_elem in self.root: | |
370 | + | |
371 | + if self.rejected(file_elem): | |
372 | + continue | |
373 | + | |
374 | + items = file_elem.findall("ann") | |
375 | + items.extend(file_elem.findall("s_ann")) | |
376 | + for ann_elem in items: | |
377 | + ann_name = ann_elem.find("annName").text | |
378 | + if ann_name == user and self.finished(ann_elem) and not self.returned(ann_elem): | |
379 | + n += 1 | |
380 | + | |
381 | + return n | |
... | ... |
gui/dfs/msg/__init__.py
0 → 100644
1 | +++ a/gui/dfs/msg/__init__.py | |
... | ... |
gui/dfs/msg/message.py
0 → 100644
1 | +++ a/gui/dfs/msg/message.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +__all__ = ["decode", "Message", "OkMessage", "KoMessage", "DownloadRequest" | |
3 | + , "UploadRequest", "DownloadPrimRequest", "ClientDone", "ServerDone" | |
4 | + , "NumMessage", "BoolMessage", "ReturnRequest", "CheckoutRequest", | |
5 | + "CheckinRequest", "StatsRequest"] | |
6 | + | |
7 | + | |
8 | +class Message(object): | |
9 | + | |
10 | + """ | |
11 | + Base class for SSL communication messages. | |
12 | + """ | |
13 | + | |
14 | + def __init__(self, contents=""): | |
15 | + self.contents = contents | |
16 | + | |
17 | + def get_contents(self): | |
18 | + return self.contents | |
19 | + | |
20 | + @classmethod | |
21 | + def from_contents(cls, contents): | |
22 | + return cls(contents) | |
23 | + | |
24 | + def encode(self): | |
25 | + """ | |
26 | + Prepare message to be sent over network. | |
27 | + WARNING: Do not override this method. | |
28 | + """ | |
29 | + # Add "-" on the beggining to prevent | |
30 | + # message contents from being void. | |
31 | + return self.msg_type, "-" + self.get_contents() | |
32 | + | |
33 | + | |
34 | +class NumMessage(Message): | |
35 | + | |
36 | + def __init__(self, n): | |
37 | + self.number = n | |
38 | + | |
39 | + def get_contents(self): | |
40 | + return str(self.number) | |
41 | + | |
42 | + @classmethod | |
43 | + def from_contents(cls, contents): | |
44 | + return cls(int(contents)) | |
45 | + | |
46 | + def get_number(self): | |
47 | + return self.number | |
48 | + | |
49 | + | |
50 | +class BoolMessage(Message): | |
51 | + | |
52 | + def __init__(self, b): | |
53 | + self.b = b | |
54 | + | |
55 | + def get_contents(self): | |
56 | + return "1" if self.b else "0" | |
57 | + | |
58 | + @classmethod | |
59 | + def from_contents(cls, contents): | |
60 | + return cls(bool(int(contents))) | |
61 | + | |
62 | + def get_boolean(self): | |
63 | + return self.b | |
64 | + | |
65 | +class ReturnRequest(NumMessage): | |
66 | + pass | |
67 | + | |
68 | +class CheckoutRequest(Message): | |
69 | + pass | |
70 | + | |
71 | +class CheckinRequest(NumMessage): | |
72 | + pass | |
73 | + | |
74 | +class DownloadRequest(NumMessage): | |
75 | + pass | |
76 | + | |
77 | +class DownloadPrimRequest(NumMessage): | |
78 | + pass | |
79 | + | |
80 | +class UploadRequest(NumMessage): | |
81 | + pass | |
82 | + | |
83 | +class OkMessage(Message): | |
84 | + pass | |
85 | + | |
86 | +class KoMessage(Message): | |
87 | + pass | |
88 | + | |
89 | +class StatsRequest(Message): | |
90 | + pass | |
91 | + | |
92 | +class ServerDone(Message): | |
93 | + pass | |
94 | + | |
95 | +class ClientDone(Message): | |
96 | + pass | |
97 | + | |
98 | + | |
99 | +def subclasses(cls): | |
100 | + yield cls | |
101 | + for child in cls.__subclasses__(): | |
102 | + for desc in subclasses(child): | |
103 | + yield desc | |
104 | + | |
105 | +def decode(msg_type, contents): | |
106 | + for cls in subclasses(Message): | |
107 | + if cls.msg_type == msg_type: | |
108 | + # Discard first, dummy character; | |
109 | + # Confront Message.encode method. | |
110 | + return cls.from_contents(contents[1:]) | |
111 | + | |
112 | +for i, cls in enumerate(subclasses(Message)): | |
113 | + cls.msg_type = i | |
... | ... |
gui/dfs/msg/stream.py
0 → 100644
1 | +++ a/gui/dfs/msg/stream.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import struct | |
3 | + | |
4 | +from message import decode | |
5 | + | |
6 | +__all__ = ["read_msg", "write_msg", "EndOfStream"] | |
7 | + | |
8 | +class EndOfStream(Exception): | |
9 | + pass | |
10 | + | |
11 | +class BadMessage(Exception): | |
12 | + pass | |
13 | + | |
14 | +def read_msg(connstream): | |
15 | + encoded = read_encoded(connstream) | |
16 | + return decode(*encoded) | |
17 | + | |
18 | +def accept_msg(connstream, cls): | |
19 | + msg = read_msg(connstream) | |
20 | + if type(msg) != cls: | |
21 | + raise BadMessage("Expected %s, got %s" % | |
22 | + (cls.__name__, type(msg).__name__)) | |
23 | + return msg | |
24 | + | |
25 | +def write_msg(connstream, msg): | |
26 | + encoded = msg.encode() | |
27 | + write_encoded(connstream, *encoded) | |
28 | + | |
29 | +def read_encoded(connstream): | |
30 | + _type = read_type(connstream) | |
31 | + length = read_length(connstream) | |
32 | + content = read_content(connstream, length) | |
33 | + return _type, content | |
34 | + | |
35 | +def write_encoded(connstream, _type, content): | |
36 | + write_type(connstream, _type) | |
37 | + write_length(connstream, len(content)) | |
38 | + write_content(connstream, content) | |
39 | + | |
40 | +def read_type(connstream): | |
41 | + data = read_all(connstream, 1) | |
42 | + return struct.unpack("!B", data)[0] | |
43 | + | |
44 | +def write_type(connstream, _type): | |
45 | + data = struct.pack("!B", _type) | |
46 | + write_all(connstream, data) | |
47 | + | |
48 | +def read_length(connstream): | |
49 | + data = read_all(connstream, 4) | |
50 | + return struct.unpack("!L", data)[0] | |
51 | + | |
52 | +def write_length(connstream, n): | |
53 | + data = struct.pack("!L", n) | |
54 | + write_all(connstream, data) | |
55 | + | |
56 | +def read_content(connstream, length): | |
57 | + return read_all(connstream, length) | |
58 | + | |
59 | +def write_content(connstream, content): | |
60 | + write_all(connstream, content) | |
61 | + | |
62 | +def write_all(connstream, data): | |
63 | + k = connstream.write(data) | |
64 | + if k != len(data): | |
65 | + raise Exception("Did not send all data !") | |
66 | + | |
67 | +def read_all(connstream, k): | |
68 | + data = "" | |
69 | + while k > 0: | |
70 | + # part = connstream.read() | |
71 | + part = connstream.read(min(k, 1024)) | |
72 | + if len(part) == 0: | |
73 | + raise EndOfStream | |
74 | + k -= len(part) | |
75 | + # if k < 0: | |
76 | + # raise Exception("Received more than expected !") | |
77 | + data += part | |
78 | + return data | |
79 | + | |
... | ... |
gui/dfs/repo.py
0 → 100644
1 | +++ a/gui/dfs/repo.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import os | |
3 | +import shutil | |
4 | +import pysvn | |
5 | + | |
6 | +class Repo: | |
7 | + | |
8 | + def __init__(self, path, login, passwd): | |
9 | + self.path = path | |
10 | + def get_login(_realm, _username, _may_save): | |
11 | + return True, login, passwd, False | |
12 | + self.svn = pysvn.Client() | |
13 | + self.svn.callback_get_login = get_login | |
14 | + self.svn.update(self.path) | |
15 | + | |
16 | + def add(self, src_path): | |
17 | + part_name = os.path.basename(src_path) | |
18 | + dest_path = os.path.join(self.new_path(), part_name) | |
19 | + | |
20 | + if (not self.svn.info(dest_path) is None) or (os.path.exists(dest_path)): | |
21 | + raise Exception(src_path+" already present in repository.") | |
22 | + | |
23 | + shutil.copy(src_path, dest_path) | |
24 | + self.svn.add(dest_path) | |
25 | + | |
26 | + def remove(self, filename, anno_per_file): | |
27 | + paths = set() | |
28 | + paths.add(os.path.join(self.new_path(), filename)) | |
29 | + for i in range(anno_per_file): | |
30 | + paths.add(self.upload_path(filename, i)) | |
31 | + paths.add(os.path.join(self.finito_path(), filename)) | |
32 | + | |
33 | + removed = [] | |
34 | + for path in paths: | |
35 | + if os.path.exists(path): | |
36 | + self.svn.remove(path) | |
37 | + removed.append(path) | |
38 | + | |
39 | + return removed | |
40 | + | |
41 | + def db_path(self): | |
42 | + return os.path.join(self.path, "db.xml") | |
43 | + | |
44 | + def new_path(self): | |
45 | + return os.path.join(self.path, "new") | |
46 | + | |
47 | + def ann_path(self, idx): | |
48 | + ids = chr(idx+ord('A')) | |
49 | + tail = os.path.join("annotation", ids) | |
50 | + return os.path.join(self.path, tail) | |
51 | + | |
52 | + def finito_path(self): | |
53 | + return os.path.join(self.path, "adjudication") | |
54 | + | |
55 | + def upload_path(self, file_name, idx): | |
56 | + return os.path.join(self.ann_path(idx), file_name) | |
57 | + | |
58 | + def upload_prim_path(self, file_name): | |
59 | + return os.path.join(self.finito_path(), file_name) | |
60 | + | |
61 | + def read_data(self, path): | |
62 | + with open(path) as src: | |
63 | + return src.read() | |
64 | + | |
65 | + def read_contents(self, path, exts): | |
66 | + contents = {} | |
67 | + for ext in exts: | |
68 | + contents[ext] = self.read_data(path + ext) | |
69 | + return contents | |
70 | + | |
71 | + def write_data(self, path, data): | |
72 | + with open(path, "w") as dest: | |
73 | + dest.write(data) | |
74 | + | |
75 | + def write_contents(self, path, contents): | |
76 | + for ext, data in contents.iteritems(): | |
77 | + dest_path = path + ext | |
78 | + self.write_data(dest_path, data) | |
79 | + if self.svn.info(dest_path) is None: | |
80 | + self.svn.add(dest_path) | |
81 | + | |
82 | + def upload(self, file_id, idx, contents): | |
83 | + self.write_contents(self.upload_path(file_id, idx), contents) | |
84 | + | |
85 | + def upload_prim(self, file_id, contents): | |
86 | + self.write_contents(self.upload_prim_path(file_id), contents) | |
87 | + | |
88 | + def download(self, file_id, exts): | |
89 | + return self.read_contents(os.path.join(self.new_path(), file_id), exts) | |
90 | + | |
91 | + def download_prim(self, file_id, exts, anno_per_file): | |
92 | + result = [] | |
93 | + for i in range(anno_per_file): | |
94 | + conts = self.read_contents(os.path.join(self.ann_path(i), file_id), exts) | |
95 | + result.append(conts) | |
96 | + return result | |
97 | + | |
98 | + def checkout(self, file_id, idx, exts): | |
99 | + src_paths = [ self.upload_path(file_id, idx) | |
100 | + , os.path.join(self.new_path(), file_id) ] | |
101 | + for path in src_paths: | |
102 | + if all(os.path.exists(path + ext) for ext in exts): | |
103 | + return self.read_contents(path, exts) | |
104 | + | |
105 | + def checkout_prim(self, file_id, exts): | |
106 | + path = os.path.join(self.finito_path(), file_id) | |
107 | + if all(os.path.exists(path + ext) for ext in exts): | |
108 | + return self.read_contents(path, exts) | |
109 | + | |
110 | + def commit(self, message): | |
111 | + self.svn.checkin(self.path, message, recurse=True) | |
112 | + | |
113 | + def revert(self): | |
114 | + self.svn.revert(self.path, recurse=True) | |
... | ... |
gui/dfs/server.py
0 → 100644
1 | +++ a/gui/dfs/server.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import sys | |
3 | +import os | |
4 | +import traceback | |
5 | +import socket, ssl | |
6 | +import random | |
7 | +from optparse import OptionParser | |
8 | +import signal | |
9 | + | |
10 | +from utils import validate_user, UserInvalid, send_contents, receive_contents | |
11 | +from msg.stream import read_msg, write_msg, accept_msg, EndOfStream, BadMessage | |
12 | +from msg.message import * | |
13 | +from repo import Repo | |
14 | +from database import Database | |
15 | +from config import Config | |
16 | + | |
17 | +class SSLServer: | |
18 | + | |
19 | + def __init__(self, host, port, backlog, cert_file, key_file, | |
20 | + repository, svn_login, svn_passwd, pass_file, | |
21 | + users_file, file_exts, anno_per_file, log=None): | |
22 | + """ | |
23 | + Initialize SSL server. | |
24 | + | |
25 | + params: | |
26 | + ======= | |
27 | + host : str | |
28 | + Host name. | |
29 | + port : int | |
30 | + Port number. | |
31 | + backlog : int | |
32 | + Maximum number of waiting connections. | |
33 | + cert_file : path | |
34 | + Public PEM certificate. | |
35 | + key_file : path | |
36 | + Private key. | |
37 | + pass_file : path | |
38 | + File with client passwords. | |
39 | + users_file : path | |
40 | + File with additional users configuration. | |
41 | + repository : path | |
42 | + Subversion working copy. | |
43 | + svn_login : str | |
44 | + Subversion login. | |
45 | + svn_passwd : str | |
46 | + Subversion password. | |
47 | + file_exts : [str] | |
48 | + List of file extensions. | |
49 | + anno_per_file : int | |
50 | + Number of normal annotators per file (1 or 2) | |
51 | + """ | |
52 | + self.cert_file = cert_file | |
53 | + self.key_file = key_file | |
54 | + self.pass_file = pass_file | |
55 | + self.users_file = users_file | |
56 | + self.wc = Repo(repository, svn_login, svn_passwd) | |
57 | + self.file_exts = file_exts | |
58 | + if log is not None: | |
59 | + self.log = open(log, "a") | |
60 | + else: | |
61 | + self.log = sys.stdout | |
62 | + self.bound = bind_socket(host, port, backlog=backlog) | |
63 | + self.serving = None | |
64 | + self.anno_per_file = anno_per_file | |
65 | + | |
66 | + def run(self): | |
67 | + while True: | |
68 | + newsocket, fromaddr = self.bound.accept() | |
69 | + self.serving = fromaddr | |
70 | + connstream = None | |
71 | + try: | |
72 | + connstream = ssl.wrap_socket(newsocket, | |
73 | + server_side=True, | |
74 | + certfile=self.cert_file, | |
75 | + keyfile=self.key_file, | |
76 | + ssl_version=ssl.PROTOCOL_TLSv1) | |
77 | + | |
78 | + login = validate_user(connstream, self.pass_file) | |
79 | + self.serve_client(connstream, login) | |
80 | + except UserInvalid as login: | |
81 | + print >> self.log, "AUTH ERROR:", login | |
82 | + except BadMessage as info: | |
83 | + print >> self.log, "BAD MESSAGE ERROR:", info | |
84 | + print >> self.log, traceback.format_exc().strip() | |
85 | + except EndOfStream: | |
86 | + print >> self.log, "UNEXPECTED END OF STREAM:" | |
87 | + print >> self.log, traceback.format_exc().strip() | |
88 | + except: | |
89 | + print >> self.log, "UNEXPECTED ERROR:" | |
90 | + print >> self.log, traceback.format_exc().strip() | |
91 | + finally: | |
92 | + try: | |
93 | + if not connstream is None: | |
94 | + connstream.shutdown(socket.SHUT_RDWR) | |
95 | + except: | |
96 | + print >> self.log, "UNEXPECTED ERROR:" | |
97 | + print >> self.log, traceback.format_exc().strip() | |
98 | + finally: | |
99 | + if not connstream is None: | |
100 | + connstream.close() | |
101 | + self.serving = None | |
102 | + | |
103 | + def exit(self): | |
104 | + if self.serving is not None: | |
105 | + print ("Client from %s connected to the server" | |
106 | + % str(self.serving)) | |
107 | + else: | |
108 | + sys.exit(0) | |
109 | + | |
110 | + def process_msg(self, msg, stream, login): | |
111 | + if type(msg) == UploadRequest: | |
112 | + self.process_upload_msg(msg, stream, login, checkin=False) | |
113 | + elif type(msg) == CheckinRequest: | |
114 | + self.process_upload_msg(msg, stream, login, checkin=True) | |
115 | + elif type(msg) == CheckoutRequest: | |
116 | + self.process_checkout_msg(msg, stream, login) | |
117 | + elif type(msg) == DownloadRequest: | |
118 | + self.process_download_msg(msg, stream, login) | |
119 | + elif type(msg) == DownloadPrimRequest: | |
120 | + self.process_download_prim_msg(msg, stream, login) | |
121 | + elif type(msg) == ReturnRequest: | |
122 | + self.process_return_msg(msg, stream, login) | |
123 | + elif type(msg) == StatsRequest: | |
124 | + self.process_stats_msg(msg, stream, login) | |
125 | + else: | |
126 | + print >> self.log, login, "sent:", msg | |
127 | + | |
128 | + def process_return_msg(self, msg, stream, login): | |
129 | + reason = accept_msg(stream, Message).get_contents().decode("utf-8") | |
130 | + | |
131 | + usr_cfg = Config(self.users_file) | |
132 | + try: | |
133 | + user_adj = login in usr_cfg["auth.adjudicators"].split() | |
134 | + except KeyError: | |
135 | + user_adj = False | |
136 | + | |
137 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
138 | + n = msg.get_number() | |
139 | + for _ in range(n): | |
140 | + file_id = accept_msg(stream, Message).get_contents() | |
141 | + is_adj = accept_msg(stream, BoolMessage).get_boolean() | |
142 | + if is_adj and not user_adj: | |
143 | + write_msg(stream, KoMessage()) | |
144 | + print >> self.log, "Upload error - user " + login + " tried to upload super annotated file " + file_id + " but is not a super annotator" | |
145 | + continue | |
146 | + else: | |
147 | + error = db.upload_prevention(file_id, login, is_adj) | |
148 | + if error is not None: | |
149 | + write_msg(stream, KoMessage()) | |
150 | + print >> self.log, "Upload error by user " + login + ". Details: "+error | |
151 | + continue | |
152 | + | |
153 | + write_msg(stream, OkMessage()) | |
154 | + contents = receive_contents(stream) | |
155 | + for key in contents.keys(): | |
156 | + if key not in self.file_exts: | |
157 | + print >> self.log, "Skipped file uploaded by user " + login + ". Filename:"+file_id+key | |
158 | + del contents[key] | |
159 | + | |
160 | + if is_adj is False: | |
161 | + idx = db.return_file(file_id, login, reason) | |
162 | + self.wc.upload(file_id, idx, contents) | |
163 | + else: | |
164 | + db.return_file_prim(file_id, login, reason) | |
165 | + self.wc.upload_prim(file_id, contents) | |
166 | + | |
167 | + db.save() | |
168 | + accept_msg(stream, ClientDone) | |
169 | + self.wc.commit("return request from %s" | |
170 | + % (login)) | |
171 | + write_msg(stream, ServerDone()) | |
172 | + | |
173 | + | |
174 | + def process_upload_msg(self, msg, stream, login, checkin=False): | |
175 | + usr_cfg = Config(self.users_file) | |
176 | + try: | |
177 | + user_adj = login in usr_cfg["auth.adjudicators"].split() | |
178 | + except KeyError: | |
179 | + user_adj = False | |
180 | + | |
181 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
182 | + n = msg.get_number() | |
183 | + for _ in range(n): | |
184 | + file_id = accept_msg(stream, Message).get_contents() | |
185 | + is_adj = accept_msg(stream, BoolMessage).get_boolean() | |
186 | + if is_adj and not user_adj: | |
187 | + write_msg(stream, KoMessage()) | |
188 | + print >> self.log, "Upload error - user " + login + " tried to upload super annotated file " + file_id + " but is not a super annotator" | |
189 | + continue | |
190 | + else: | |
191 | + error = db.upload_prevention(file_id, login, is_adj) | |
192 | + if error is not None: | |
193 | + write_msg(stream, KoMessage()) | |
194 | + print >> self.log, "Upload error by user " + login + ". Details: "+error | |
195 | + continue | |
196 | + | |
197 | + write_msg(stream, OkMessage()) | |
198 | + contents = receive_contents(stream) | |
199 | + for key in contents.keys(): | |
200 | + if key not in self.file_exts: | |
201 | + print >> self.log, "Skipped file uploaded by user " + login + ". Filename:"+file_id+key | |
202 | + del contents[key] | |
203 | + | |
204 | + if is_adj is False: | |
205 | + if checkin: | |
206 | + idx = db.upload(file_id, login) | |
207 | + else: | |
208 | + idx = db.upload_id(file_id, login) | |
209 | + self.wc.upload(file_id, idx, contents) | |
210 | + else: | |
211 | + if checkin: | |
212 | + db.upload_prim(file_id, login) | |
213 | + self.wc.upload_prim(file_id, contents) | |
214 | + | |
215 | + if checkin: | |
216 | + db.save() | |
217 | + accept_msg(stream, ClientDone) | |
218 | + self.wc.commit("%s request from %s" | |
219 | + % ("checkin" if checkin else "upload", login)) | |
220 | + write_msg(stream, ServerDone()) | |
221 | + | |
222 | + def process_checkout_msg(self, msg, stream, login): | |
223 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
224 | + | |
225 | + ann_files = db.owns_normal(login) | |
226 | + write_msg(stream, NumMessage(len(ann_files))) | |
227 | + for file_id in ann_files: | |
228 | + write_msg(stream, Message(file_id)) | |
229 | + idx = db.upload_id(file_id, login) | |
230 | + contents = self.wc.checkout(file_id, idx, self.file_exts) | |
231 | + send_contents(stream, contents) | |
232 | + | |
233 | + adj_files = db.owns_super(login) | |
234 | + write_msg(stream, NumMessage(len(adj_files))) | |
235 | + write_msg(stream, NumMessage(self.anno_per_file)) | |
236 | + for file_id in adj_files: | |
237 | + write_msg(stream, Message(file_id)) | |
238 | + | |
239 | + contents = self.wc.download_prim(file_id, self.file_exts, self.anno_per_file) | |
240 | + for conts in contents: | |
241 | + send_contents(stream, conts) | |
242 | + | |
243 | + conts3 = self.wc.checkout_prim(file_id, self.file_exts) | |
244 | + if conts3 is None: | |
245 | + write_msg(stream, BoolMessage(False)) | |
246 | + else: | |
247 | + write_msg(stream, BoolMessage(True)) | |
248 | + send_contents(stream, conts3) | |
249 | + | |
250 | + accept_msg(stream, ClientDone) | |
251 | + #self.wc.commit("checkout request from %s" % login) | |
252 | + write_msg(stream, ServerDone()) | |
253 | + | |
254 | + def process_stats_msg(self, msg, stream, login): | |
255 | + usr_cfg = Config(self.users_file) | |
256 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
257 | + | |
258 | + write_msg(stream, NumMessage(db.finished_count(login))) | |
259 | + | |
260 | + accept_msg(stream, ClientDone) | |
261 | + write_msg(stream, ServerDone()) | |
262 | + | |
263 | + def process_download_msg(self, msg, stream, login): | |
264 | + usr_cfg = Config(self.users_file) | |
265 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
266 | + | |
267 | + down_num = msg.get_number() | |
268 | + owns_num = len(db.owns_normal(login)) | |
269 | + limit = float('inf') | |
270 | + try: | |
271 | + limit = int(usr_cfg["limits.%s.annotation" % login]) | |
272 | + except KeyError: | |
273 | + limit = int(usr_cfg["limits.annotation"]) | |
274 | + if down_num + owns_num > limit: | |
275 | + down_num = max(0, limit - owns_num) | |
276 | + | |
277 | + for_ann_fixed, for_ann = db.for_annotation(login) | |
278 | + if len(for_ann) + len(for_ann_fixed) == 0: | |
279 | + print >> self.log, "User " + login + " has no more files to download for annotation." | |
280 | + | |
281 | + down_files = sample(for_ann_fixed, down_num) | |
282 | + down_num = max(0, down_num - len(down_files)) | |
283 | + down_files = down_files + sample(for_ann, down_num) | |
284 | + | |
285 | + write_msg(stream, NumMessage(len(down_files))) | |
286 | + | |
287 | + for file_id in down_files: | |
288 | + fixed_idx = db.download(file_id, login) | |
289 | + write_msg(stream, Message(file_id)) | |
290 | + if fixed_idx is None: | |
291 | + contents = self.wc.download(file_id, self.file_exts) | |
292 | + else: | |
293 | + contents = self.wc.checkout(file_id, fixed_idx, self.file_exts) | |
294 | + send_contents(stream, contents) | |
295 | + db.save() | |
296 | + accept_msg(stream, ClientDone) | |
297 | + self.wc.commit("download request from %s" % login) | |
298 | + write_msg(stream, ServerDone()) | |
299 | + | |
300 | + def process_download_prim_msg(self, msg, stream, login): | |
301 | + usr_cfg = Config(self.users_file) | |
302 | + try: | |
303 | + adjudicators = usr_cfg["auth.adjudicators"].split() | |
304 | + except KeyError: | |
305 | + adjudicators = [] | |
306 | + if login in adjudicators: | |
307 | + write_msg(stream, OkMessage()) | |
308 | + else: | |
309 | + write_msg(stream, KoMessage()) | |
310 | + return | |
311 | + db = Database(self.wc.db_path(), self.anno_per_file) | |
312 | + | |
313 | + down_num = msg.get_number() | |
314 | + owns_num = len(db.owns_super(login)) | |
315 | + limit = float('inf') | |
316 | + try: | |
317 | + limit = int(usr_cfg["limits.%s.adjudication" % login]) | |
318 | + except KeyError: | |
319 | + limit = int(usr_cfg["limits.adjudication"]) | |
320 | + if down_num + owns_num > limit: | |
321 | + down_num = max(0, limit - owns_num) | |
322 | + | |
323 | + for_adj_fixed, for_adj = db.for_adjudication(login) | |
324 | + if len(for_adj) + len(for_adj_fixed) == 0: | |
325 | + print >> self.log, "User " + login + " has no more files to download for superannotation." | |
326 | + | |
327 | + down_files = sample(for_adj_fixed, down_num) | |
328 | + down_num = max(0, down_num - len(down_files)) | |
329 | + down_files = down_files + sample(for_adj, down_num) | |
330 | + | |
331 | + write_msg(stream, NumMessage(len(down_files))) | |
332 | + write_msg(stream, NumMessage(self.anno_per_file)) | |
333 | + for file_id in down_files: | |
334 | + write_msg(stream, Message(file_id)) | |
335 | + fixed = db.download_prim(file_id, login) | |
336 | + | |
337 | + contents = self.wc.download_prim(file_id, self.file_exts, self.anno_per_file) | |
338 | + for conts in contents: | |
339 | + send_contents(stream, conts) | |
340 | + | |
341 | + if fixed: | |
342 | + write_msg(stream, BoolMessage(True)) | |
343 | + contents = self.wc.checkout_prim(file_id, self.file_exts) | |
344 | + send_contents(stream, contents) | |
345 | + else: | |
346 | + write_msg(stream, BoolMessage(False)) | |
347 | + | |
348 | + db.save() | |
349 | + accept_msg(stream, ClientDone) | |
350 | + self.wc.commit("download' request from %s" % login) | |
351 | + write_msg(stream, ServerDone()) | |
352 | + | |
353 | + def serve_client(self, connstream, login): | |
354 | + msg = read_msg(connstream) | |
355 | + try: | |
356 | + self.process_msg(msg, connstream, login) | |
357 | + except: | |
358 | + self.wc.revert() | |
359 | + raise | |
360 | + | |
361 | +def bind_socket(host, port, backlog=5): | |
362 | + bound = socket.socket() | |
363 | + bound.bind((host, port)) | |
364 | + bound.listen(backlog) | |
365 | + return bound | |
366 | + | |
367 | +def sample(population, n): | |
368 | + if len(population) > n: | |
369 | + return random.sample(population, n) | |
370 | + else: | |
371 | + return population | |
372 | + | |
373 | +if __name__ == "__main__": | |
374 | + optparser = OptionParser(usage="""usage: %prog CONFIG""") | |
375 | + (options, args) = optparser.parse_args() | |
376 | + if len(args) != 1: | |
377 | + optparser.print_help() | |
378 | + sys.exit(0) | |
379 | + | |
380 | + cfg = Config(args[0]) | |
381 | + server = SSLServer( | |
382 | + host=cfg["connection.host"], | |
383 | + port=int(cfg["connection.port"]), | |
384 | + backlog=int(cfg["connection.backlog"]), | |
385 | + cert_file=cfg["connection.certfile"], | |
386 | + key_file=cfg["connection.keyfile"], | |
387 | + repository=cfg["svn.repository"], | |
388 | + svn_login=cfg["svn.login"], | |
389 | + svn_passwd=cfg["svn.passwd"], | |
390 | + pass_file=cfg["users.passfile"], | |
391 | + users_file=cfg["users.config"], | |
392 | + file_exts=cfg["file_extensions"].split(), | |
393 | + anno_per_file=int(cfg["anno_per_file"]) | |
394 | + ) | |
395 | + | |
396 | + def handler(signum, frame): | |
397 | + server.exit() | |
398 | + | |
399 | + signal.signal(signal.SIGINT, handler) | |
400 | + | |
401 | + server.run() | |
... | ... |
gui/dfs/utils.py
0 → 100644
1 | +++ a/gui/dfs/utils.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import hashlib | |
3 | + | |
4 | +from msg.stream import accept_msg, write_msg | |
5 | +from msg.message import Message, OkMessage, KoMessage, NumMessage | |
6 | + | |
7 | +class UserInvalid(Exception): | |
8 | + pass | |
9 | + | |
10 | +def validate_user(conns, pass_file): | |
11 | + pass_dict = parse_passwd(pass_file) | |
12 | + login = accept_msg(conns, Message).get_contents() | |
13 | + passwd = accept_msg(conns, Message).get_contents() | |
14 | + if pass_dict.has_key(login) and pass_dict[login] == passwd: | |
15 | + write_msg(conns, OkMessage()) | |
16 | + return login | |
17 | + else: | |
18 | + write_msg(conns, KoMessage()) | |
19 | + raise UserInvalid(login) | |
20 | + | |
21 | +def parse_passwd(pass_file): | |
22 | + result = {} | |
23 | + with open(pass_file) as f: | |
24 | + for line in f: | |
25 | + login, passwd = line.split("=") | |
26 | + login = login.strip() | |
27 | + passwd = passwd.strip() | |
28 | + result[login] = passwd | |
29 | + return result | |
30 | + | |
31 | +def _check_sum(*args): | |
32 | + m = hashlib.sha256() | |
33 | + for arg in args: | |
34 | + m.update(arg) | |
35 | + return m | |
36 | + | |
37 | +def check_sum(*args): | |
38 | + return _check_sum(*args).digest() | |
39 | + | |
40 | +def check_hexsum(*args): | |
41 | + return _check_sum(*args).hexdigest() | |
42 | + | |
43 | +def send_contents(stream, contents): | |
44 | + write_msg(stream, NumMessage(len(contents))) | |
45 | + for ext, data in contents.iteritems(): | |
46 | + write_msg(stream, Message(ext)) | |
47 | + write_msg(stream, Message(data)) | |
48 | + write_msg(stream, Message(check_sum(ext, data))) | |
49 | + accept_msg(stream, OkMessage) | |
50 | + | |
51 | +def receive_contents(stream): | |
52 | + contents = {} | |
53 | + n = accept_msg(stream, NumMessage).get_number() | |
54 | + for _ in range(n): | |
55 | + ext = accept_msg(stream, Message).get_contents() | |
56 | + data = accept_msg(stream, Message).get_contents() | |
57 | + csum = accept_msg(stream, Message).get_contents() | |
58 | + if csum == check_sum(ext, data): | |
59 | + write_msg(stream, OkMessage()) | |
60 | + else: | |
61 | + write_msg(stream, KoMessage()) | |
62 | + contents[ext] = data | |
63 | + return contents | |
... | ... |
gui/dir_cache.py
0 → 100644
1 | +++ a/gui/dir_cache.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +"""Directory cache manipulation""" | |
3 | + | |
4 | +import os | |
5 | + | |
6 | +class DirCache: | |
7 | + | |
8 | + def __init__(self, path, size): | |
9 | + if not os.path.exists(path): | |
10 | + with open(path, "w") as f: pass | |
11 | + self.path = path | |
12 | + self.size = size | |
13 | + | |
14 | + def add(self, dir_name): | |
15 | + cache = self.get() | |
16 | + if dir_name in cache: | |
17 | + cache.remove(dir_name) | |
18 | + cache.insert(0, dir_name) | |
19 | + self.write(cache[:self.size]) | |
20 | + | |
21 | + def get(self): | |
22 | + with open(self.path) as f: | |
23 | + return [x.strip() for x in f.readlines()] | |
24 | + | |
25 | + def write(self, cache): | |
26 | + with open(self.path, "w") as f: | |
27 | + for entry in cache: | |
28 | + print >> f, entry | |
... | ... |
gui/footprints.py
0 → 100644
1 | +++ a/gui/footprints.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import os | |
3 | +import copy | |
4 | + | |
5 | +# from dfs.utils import check_hexsum as check_sum | |
6 | + | |
7 | +def getmtime(path): | |
8 | + return round(os.path.getmtime(path)) | |
9 | + | |
10 | +from utils import all_files | |
11 | + | |
12 | +class FootPrints: | |
13 | + | |
14 | + """Class for footprints file manipulation.""" | |
15 | + | |
16 | + def __init__(self, prints_path, dir_path, exts): | |
17 | + self.prints_path = prints_path | |
18 | + self.dir_path = dir_path | |
19 | + self.exts = exts | |
20 | + if not os.path.exists(prints_path): | |
21 | + with open(prints_path, "w") as f: pass | |
22 | + self.prints = self.read() | |
23 | + self.backup = copy.deepcopy(self.prints) | |
24 | + | |
25 | + def modified(self, name): | |
26 | + try: | |
27 | + # (mtime, csum) = self.get_print(name) | |
28 | + mtime = self.get_print(name) | |
29 | + except KeyError: | |
30 | + return False | |
31 | + | |
32 | + path = os.path.join(self.dir_path, name) | |
33 | + if mtime != self.print_now(path): | |
34 | + return True | |
35 | + return False | |
36 | + | |
37 | + """ sprawdza czy nie pojawily sie jakies nowe pliki, ewentualnie dodajac dla nich wpisy """ | |
38 | + def refresh(self): | |
39 | + old_prints = self.prints | |
40 | + new_prints = {} | |
41 | + for name in all_files(self.dir_path, self.exts): | |
42 | + if old_prints.has_key(name): | |
43 | + new_prints[name] = old_prints[name] | |
44 | + else: | |
45 | + path = os.path.join(self.dir_path, name) | |
46 | + new_prints[name] = self.print_now(path) | |
47 | + self.prints = new_prints | |
48 | + | |
49 | + def renew(self, name): | |
50 | + path = os.path.join(self.dir_path, name) | |
51 | + try: | |
52 | + self.prints[name] = self.print_now(path) | |
53 | + except: | |
54 | + del self.prints[name] | |
55 | + print "Footprints: "+ path + " file not found." | |
56 | + | |
57 | + def renew_all(self): | |
58 | + for key in self.prints.keys(): | |
59 | + self.renew(key) | |
60 | + | |
61 | + def get_print(self, name): | |
62 | + path = os.path.join(self.dir_path, name) | |
63 | + return self.prints[name] | |
64 | + | |
65 | + def print_now(self, path): | |
66 | + return max(getmtime(path + ext) for ext in self.exts) | |
67 | + | |
68 | + def read(self): | |
69 | + prints = {} | |
70 | + with open(self.prints_path) as f: | |
71 | + for line in f: | |
72 | + key, mtime = line.split() | |
73 | + prints[key] = float(mtime) | |
74 | + return prints | |
75 | + | |
76 | + def write(self): | |
77 | + with open(self.prints_path, "w") as f: | |
78 | + # for key, (mtime, csum) in sorted(self.prints.items()): | |
79 | + # print >> f, key, "=", mtime, csum | |
80 | + for key, mtime in sorted(self.prints.items()): | |
81 | + print >> f, key, mtime | |
82 | + | |
83 | + def save(self): | |
84 | + if self.prints != self.backup: | |
85 | + self.write() | |
... | ... |
gui/i18n.py
0 → 100644
1 | +++ a/gui/i18n.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import os, sys | |
3 | +import locale | |
4 | +import gettext | |
5 | +import const | |
6 | + | |
7 | +def get_path(): | |
8 | + '''Get the path to this script no matter how it's run.''' | |
9 | + #Determine if the application is a py/pyw or a frozen exe. | |
10 | + if hasattr(sys, 'frozen'): | |
11 | + # If run from exe | |
12 | + dir_path = os.path.dirname(sys.executable) | |
13 | + elif '__file__' in locals(): | |
14 | + # If run from py | |
15 | + dir_path = os.path.dirname(__file__) | |
16 | + else: | |
17 | + # If run from command line | |
18 | + dir_path = sys.path[0] | |
19 | + return dir_path | |
20 | + | |
21 | + | |
22 | +# Change this variable to your app name! | |
23 | +# The translation files will be under | |
24 | +# @LOCALE_DIR@/@LANGUAGE@/LC_MESSAGES/@APP_NAME@.mo | |
25 | +# | |
26 | +APP_NAME = "manager" | |
27 | +# This is ok for maemo. Not sure in a regular desktop: | |
28 | +APP_DIR = get_path() | |
29 | +LOCALE_DIR = os.path.join(APP_DIR, 'po') | |
30 | + | |
31 | +langs = ["pl"] | |
32 | +if (const.lang == "en"): | |
33 | + langs = ["en"] | |
34 | + | |
35 | +# Lets tell those details to gettext | |
36 | +gettext.install (True) | |
37 | +gettext.bindtextdomain (APP_NAME, | |
38 | + LOCALE_DIR) | |
39 | +gettext.textdomain (APP_NAME) | |
40 | +language = gettext.translation (APP_NAME, | |
41 | + LOCALE_DIR, | |
42 | + languages = langs, | |
43 | + fallback = False) | |
44 | + | |
45 | + | |
0 | 46 | \ No newline at end of file |
... | ... |
gui/img/down.png
0 → 100644
846 Bytes
gui/img/ready.png
0 → 100644
849 Bytes
gui/img/textimg_1.png
0 → 100644
522 Bytes
gui/img/textimg_2.png
0 → 100644
648 Bytes
gui/img/textimg_3.png
0 → 100644
782 Bytes
gui/img/up.png
0 → 100644
837 Bytes
gui/log_thread.py
0 → 100644
1 | +++ a/gui/log_thread.py | |
1 | +# -*- coding: utf-8 -*- | |
2 | +import wx | |
3 | +from Queue import Queue | |
4 | +from threading import Thread | |
5 | +import copy | |
6 | + | |
7 | +class RThread(Thread): | |
8 | + | |
9 | + def __init__(self, target, args, kwargs): | |
10 | + self.target = target | |
11 | + self.args = args | |
12 | + self.kwargs = kwargs | |
13 | + self.result = None | |
14 | + Thread.__init__(self) | |
15 | + | |
16 | + def run(self): | |
17 | + self.result = self.target(*self.args, **self.kwargs) | |
18 | + | |
19 | +class LogThread(Thread): | |
20 | + | |
21 | + def __init__(self, text_ctrl, target, on_end=None, args=(), kwargs={}): | |
22 | + self.text_ctrl = text_ctrl | |
23 | + self.target = target | |
24 | + self.on_end = on_end | |
25 | + self.args = args | |
26 | + self.kwargs = kwargs | |
27 | + Thread.__init__(self) | |
28 | + | |
29 | + def run(self): | |
30 | + queue = Queue() | |
31 | + kwargs = copy.copy(self.kwargs) | |
32 | + kwargs["log"] = queue | |
33 | + worker = RThread( target=self.target | |
34 | + , args=self.args | |
35 | + , kwargs=kwargs ) | |
36 | + worker.start() | |
37 | + while True: | |
38 | + msg = queue.get() | |
39 | + if msg is None: | |
40 | + break | |
41 | + wx.CallAfter(self.print_msg, msg) | |
42 | + worker.join() | |
43 | + | |
44 | + if self.on_end: | |
45 | + wx.CallAfter(self.on_end, worker.result) | |
46 | + | |
47 | + def print_msg(self, msg): | |
48 | + self.text_ctrl.AppendText(msg) | |
... | ... |
gui/manager.py
0 → 100755
1 | +++ a/gui/manager.py | |
1 | +#!/usr/bin/env python | |
2 | +# -*- coding: utf-8 -*- | |
3 | +# generated by wxGlade 0.6.3 on Tue Sep 27 12:15:56 2011 | |
4 | + | |
5 | +import os | |
6 | +import wx | |
7 | + | |
8 | +import i18n | |
9 | +_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors | |
10 | + | |
11 | +from dfs.config import Config | |
12 | +from client import run_upload, run_download, run_download_prim, run_checkout, run_return, run_check_stats | |
13 | + | |
14 | +from MainFrame import MainFrame | |
15 | +import utils | |
16 | +import const | |
17 | +from ready import Ready | |
18 | +from dir_cache import DirCache | |
19 | +from log_thread import LogThread | |
20 | +from footprints import FootPrints | |
21 | + | |
22 | +class FileManager(wx.App): | |
23 | + | |
24 | + def OnInit(self): | |
25 | + wx.InitAllImageHandlers() | |
26 | + self.main_frame = MainFrame(None, -1, "") | |
27 | + self.SetTopWindow(self.main_frame) | |
28 | + self.main_frame.Show() | |
29 | + | |
30 | + # Custom code | |
31 | + self.main_frame.set_manager(self) | |
32 | + | |
33 | + self.curr_dir = None | |
34 | + self.config = None | |
35 | + self.selection = None | |
36 | + self.connected = False | |
37 | + self.dir_cache = DirCache(const.dir_cache_name, size=5) | |
38 | + self.refresh_thread = utils.RefreshThread(self, delay=2) | |
39 | + self.refresh_thread.start() | |
40 | + | |
41 | + utils.fill_dir_menu(self.main_frame, self.dir_cache.get()) | |
42 | + # End custom code | |
43 | + | |
44 | + return 1 | |
45 | + | |
46 | + def reconf_directory(self): | |
47 | + if not self.curr_dir: | |
48 | + return | |
49 | + config_path = const.path(self.curr_dir, const.config_name) | |
50 | + if utils.create_dir_config( config_path, self.main_frame | |
51 | + , config=self.config ): | |
52 | + self.config = Config(config_path) | |
53 | + self.refresh() | |
54 | + | |
55 | + def set_directory(self, path): | |
56 | + config_path = const.path(path, const.config_name) | |
57 | + if not os.path.exists(config_path): | |
58 | + if not utils.create_dir_config(config_path, self.main_frame): | |
59 | + return | |
60 | + self.config = Config(config_path) | |
61 | + self.ready = Ready(const.path(path, const.ready_name)) | |
62 | + self.curr_dir = path | |
63 | + self.dir_cache.add(path) | |
64 | + self.main_frame.SetTitle(path) | |
65 | + self.refresh() | |
66 | + | |
67 | + def begin_conn(self): | |
68 | + if not self.curr_dir: | |
69 | + wx.MessageBox(_('Directory not set'), _('Info')) | |
70 | + return False | |
71 | + if self.connected: | |
72 | + wx.MessageBox(_('Another operation running'), _('Info')) | |
73 | + return False | |
74 | + self.connected = True | |
75 | + return True | |
76 | + | |
77 | + def end_conn(self, op_result): | |
78 | + self.connected = False | |
79 | + | |
80 | + def upload(self, files, checkin=False): | |
81 | + if not self.begin_conn(): | |
82 | + return | |
83 | + | |
84 | + def on_end(result): | |
85 | + self.end_conn(result) | |
86 | + if checkin: | |
87 | + return | |
88 | + if result == True: | |
89 | + prints = self.footprints() | |
90 | + for file_name in files: | |
91 | + prints.renew(file_name) | |
92 | + prints.save() | |
93 | + | |
94 | + logger = LogThread( text_ctrl=self.main_frame.log_ctrl | |
95 | + , target=run_upload | |
96 | + , args=( self.config["login"] | |
97 | + , self.config["passwd"] | |
98 | + , const.conn_host | |
99 | + , const.conn_port | |
100 | + , const.conn_cert | |
101 | + , files | |
102 | + , self.curr_dir | |
103 | + , self.config["extensions"].split() | |
104 | + , checkin ) | |
105 | + , on_end=on_end ) | |
106 | + logger.start() | |
107 | + | |
108 | + def download(self, n): | |
109 | + if not self.begin_conn(): | |
110 | + return | |
111 | + logger = LogThread( text_ctrl=self.main_frame.log_ctrl | |
112 | + , target=run_download | |
113 | + , args=( self.config["login"] | |
114 | + , self.config["passwd"] | |
115 | + , const.conn_host | |
116 | + , const.conn_port | |
117 | + , const.conn_cert | |
118 | + , n | |
119 | + , self.curr_dir ) | |
120 | + , on_end=self.end_conn ) | |
121 | + logger.start() | |
122 | + | |
123 | + def download_prim(self, n): | |
124 | + if not self.begin_conn(): | |
125 | + return | |
126 | + logger = LogThread( text_ctrl=self.main_frame.log_ctrl | |
127 | + , target=run_download_prim | |
128 | + , args=( self.config["login"] | |
129 | + , self.config["passwd"] | |
130 | + , const.conn_host | |
131 | + , const.conn_port | |
132 | + , const.conn_cert | |
133 | + , n | |
134 | + , self.curr_dir ) | |
135 | + , on_end=self.end_conn ) | |
136 | + logger.start() | |
137 | + | |
138 | + def checkout(self): | |
139 | + if not self.begin_conn(): | |
140 | + return | |
141 | + | |
142 | + def on_end(result): | |
143 | + self.end_conn(result) | |
144 | + if result == True: | |
145 | + prints = self.footprints() | |
146 | + files_before = prints.prints.keys() | |
147 | + prints.renew_all() | |
148 | + prints.save() | |
149 | + | |
150 | + | |
151 | + logger = LogThread( text_ctrl=self.main_frame.log_ctrl | |
152 | + , target=run_checkout | |
153 | + , args=( self.config["login"] | |
154 | + , self.config["passwd"] | |
155 | + , const.conn_host | |
156 | + , const.conn_port | |
157 | + , const.conn_cert | |
158 | + , self.curr_dir | |
159 | + , self.config["extensions"].split() ) | |
160 | + , on_end=on_end ) | |
161 | + logger.start() | |
162 | + | |
163 | + def return_files(self, files, reason): | |
164 | + if not self.begin_conn(): | |
165 | + return | |
166 | + | |
167 | + def on_end(result): | |
168 | + self.end_conn(result) | |
169 | + | |
170 | + logger = LogThread( text_ctrl=self.main_frame.log_ctrl | |
171 | + , target=run_return | |
172 | + , args=( self.config["login"] | |
173 | + , self.config["passwd"] | |
174 | + , const.conn_host | |
175 | + , const.conn_port | |
176 | + , const.conn_cert | |
177 | + , files | |
178 | + , reason | |
179 | + , self.curr_dir | |
180 | + , self.config["extensions"].split() | |
181 | + ) | |
182 | + , on_end=on_end ) | |
183 | + logger.start() | |
184 | + | |
185 | + def footprints(self): | |
186 | + return FootPrints( const.path(self.curr_dir, const.footprints_name) | |
187 | + , self.curr_dir | |
188 | + , self.config["extensions"].split() ) | |
189 | + | |
190 | + def refresh(self): | |
191 | + if self.curr_dir is None: | |
192 | + return | |
193 | + | |
194 | + ready = self.ready.get_entries() | |
195 | + extensions = self.config["extensions"].split() | |
196 | + | |
197 | + footprints = self.footprints() | |
198 | + footprints.refresh() | |
199 | + | |
200 | + data = utils.normal_entries( | |
201 | + self.curr_dir, | |
202 | + extensions, | |
203 | + ready, | |
204 | + footprints) | |
205 | + | |
206 | + shown = self.main_frame.files_list.shown_data() | |
207 | + | |
208 | + if utils.differ(data, shown): | |
209 | + print "Data changed !" | |
210 | + self.main_frame.files_list.show(data) | |
211 | + footprints.save() | |
212 | + | |
213 | + data = utils.super_entries( | |
214 | + self.curr_dir, | |
215 | + extensions, | |
216 | + ready, | |
217 | + footprints) | |
218 | + | |
219 | + shown = self.main_frame.super_files_list.shown_data() | |
220 | + | |
221 | + if utils.differ(data, shown): | |
222 | + print "Data changed !" | |
223 | + self.main_frame.super_files_list.show(data) | |
224 | + | |
225 | + footprints.save() | |
226 | + | |
227 | + def check_stats(self): | |
228 | + if not self.begin_conn(): | |
229 | + return | |
230 | + | |
231 | + def on_end(result): | |
232 | + self.end_conn(result) | |
233 | + | |
234 | + logger = LogThread( text_ctrl=self.main_frame.log_ctrl | |
235 | + , target=run_check_stats | |
236 | + , args=( self.config["login"] | |
237 | + , self.config["passwd"] | |
238 | + , const.conn_host | |
239 | + , const.conn_port | |
240 | + , const.conn_cert ) | |
241 | + , on_end=on_end ) | |
242 | + logger.start() | |
243 | + | |
244 | +# end of class FileManager | |
245 | + | |
246 | +if __name__ == "__main__": | |
247 | + manager = FileManager(0) | |
248 | + manager.MainLoop() | |
249 | + manager.refresh_thread.exitFlag = True | |
... | ... |