Commit 44cbbee54dc5c5b714b914d91509f922cd9e7bed

Authored by Mateusz Kopeć
0 parents

initial

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
  1 +++ a/example_configuration/passwd
  1 +adj1 = adj1
  2 +adj2 = adj2
  3 +ann1 = ann1
  4 +ann2 = ann2
  5 +ann3 = ann3
  6 +ann4 = ann4
... ...
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
... ...