From 44cbbee54dc5c5b714b914d91509f922cd9e7bed Mon Sep 17 00:00:00 2001
From: Mateusz Kopeć <mkopec87@gmail.com>
Date: Wed, 23 Jan 2013 13:24:08 +0100
Subject: [PATCH] initial

---
 README                                  | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 dfs/__init__.py                         |   0
 dfs/config.py                           |  49 +++++++++++++++++++++++++++++++++++++++++++++++++
 dfs/database.py                         | 381 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 dfs/msg/__init__.py                     |   0
 dfs/msg/message.py                      | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 dfs/msg/stream.py                       |  79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 dfs/repo.py                             | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 dfs/server.py                           | 401 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 dfs/utils.py                            |  63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 example_configuration/passwd            |   6 ++++++
 example_configuration/server.cfg        |  23 +++++++++++++++++++++++
 example_configuration/ssl.conf/cert.pem |  16 ++++++++++++++++
 example_configuration/ssl.conf/key.pem  |  16 ++++++++++++++++
 example_configuration/users.cfg         |  12 ++++++++++++
 gui/CheckoutNumberDialog.py             |  64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/ConfigDialog.py                     |  62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/DownloadNumberDialog.py             |  60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/LogDialog.py                        |  42 ++++++++++++++++++++++++++++++++++++++++++
 gui/LoginDialog.py                      |  61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/MainFrame.py                        | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/ReturnReasonDialog.py               |  69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/SortableListCtrl.py                 | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/client.py                           | 470 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/const.py                            |  36 ++++++++++++++++++++++++++++++++++++
 gui/data/.dir.lst                       |   5 +++++
 gui/data/cert.pem                       |  16 ++++++++++++++++
 gui/data/global.cfg                     |  21 +++++++++++++++++++++
 gui/dfs.wxg                             | 367 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/dfs/__init__.py                     |   0
 gui/dfs/config.py                       |  49 +++++++++++++++++++++++++++++++++++++++++++++++++
 gui/dfs/database.py                     | 381 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/dfs/msg/__init__.py                 |   0
 gui/dfs/msg/message.py                  | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/dfs/msg/stream.py                   |  79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/dfs/repo.py                         | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/dfs/server.py                       | 401 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/dfs/utils.py                        |  63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/dir_cache.py                        |  28 ++++++++++++++++++++++++++++
 gui/footprints.py                       |  85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/i18n.py                             |  45 +++++++++++++++++++++++++++++++++++++++++++++
 gui/img/down.png                        | Bin 0 -> 846 bytes
 gui/img/ready.png                       | Bin 0 -> 849 bytes
 gui/img/textimg_1.png                   | Bin 0 -> 522 bytes
 gui/img/textimg_2.png                   | Bin 0 -> 648 bytes
 gui/img/textimg_3.png                   | Bin 0 -> 782 bytes
 gui/img/up.png                          | Bin 0 -> 837 bytes
 gui/log_thread.py                       |  48 ++++++++++++++++++++++++++++++++++++++++++++++++
 gui/manager.py                          | 249 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/po/en/LC_MESSAGES/manager.po        | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/po/generate_pot.sh                  |   1 +
 gui/po/manager.pot                      | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/po/pl/LC_MESSAGES/manager.po        | 354 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gui/ready.py                            |  42 ++++++++++++++++++++++++++++++++++++++++++
 gui/utils.py                            | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 installation.sh                         |  43 +++++++++++++++++++++++++++++++++++++++++++
 scripts/add_files.py                    | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/clusters.py                     | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/db_averages_stats.py            | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/db_detailed_stats.py            | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/db_stats.py                     | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/extract_files.py                |  98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/fill_empty_repo.sh              |  29 +++++++++++++++++++++++++++++
 scripts/fix_files.py                    | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/quasi.py                        | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/reject_files.py                 |  78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/remove_files.py                 |  64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/stats_atlas.py                  | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 68 files changed, 7693 insertions(+), 0 deletions(-)
 create mode 100644 README
 create mode 100644 dfs/__init__.py
 create mode 100644 dfs/config.py
 create mode 100644 dfs/database.py
 create mode 100644 dfs/msg/__init__.py
 create mode 100644 dfs/msg/message.py
 create mode 100644 dfs/msg/stream.py
 create mode 100644 dfs/repo.py
 create mode 100644 dfs/server.py
 create mode 100644 dfs/utils.py
 create mode 100644 example_configuration/passwd
 create mode 100644 example_configuration/server.cfg
 create mode 100644 example_configuration/ssl.conf/cert.pem
 create mode 100644 example_configuration/ssl.conf/key.pem
 create mode 100644 example_configuration/users.cfg
 create mode 100644 gui/CheckoutNumberDialog.py
 create mode 100644 gui/ConfigDialog.py
 create mode 100644 gui/DownloadNumberDialog.py
 create mode 100644 gui/LogDialog.py
 create mode 100644 gui/LoginDialog.py
 create mode 100644 gui/MainFrame.py
 create mode 100644 gui/ReturnReasonDialog.py
 create mode 100644 gui/SortableListCtrl.py
 create mode 100644 gui/client.py
 create mode 100644 gui/const.py
 create mode 100644 gui/data/.dir.lst
 create mode 100644 gui/data/cert.pem
 create mode 100644 gui/data/global.cfg
 create mode 100644 gui/dfs.wxg
 create mode 100644 gui/dfs/__init__.py
 create mode 100644 gui/dfs/config.py
 create mode 100644 gui/dfs/database.py
 create mode 100644 gui/dfs/msg/__init__.py
 create mode 100644 gui/dfs/msg/message.py
 create mode 100644 gui/dfs/msg/stream.py
 create mode 100644 gui/dfs/repo.py
 create mode 100644 gui/dfs/server.py
 create mode 100644 gui/dfs/utils.py
 create mode 100644 gui/dir_cache.py
 create mode 100644 gui/footprints.py
 create mode 100644 gui/i18n.py
 create mode 100644 gui/img/down.png
 create mode 100644 gui/img/ready.png
 create mode 100644 gui/img/textimg_1.png
 create mode 100644 gui/img/textimg_2.png
 create mode 100644 gui/img/textimg_3.png
 create mode 100644 gui/img/up.png
 create mode 100644 gui/log_thread.py
 create mode 100755 gui/manager.py
 create mode 100644 gui/po/en/LC_MESSAGES/manager.po
 create mode 100755 gui/po/generate_pot.sh
 create mode 100644 gui/po/manager.pot
 create mode 100644 gui/po/pl/LC_MESSAGES/manager.po
 create mode 100644 gui/ready.py
 create mode 100644 gui/utils.py
 create mode 100644 installation.sh
 create mode 100644 scripts/add_files.py
 create mode 100755 scripts/clusters.py
 create mode 100755 scripts/db_averages_stats.py
 create mode 100755 scripts/db_detailed_stats.py
 create mode 100644 scripts/db_stats.py
 create mode 100755 scripts/extract_files.py
 create mode 100755 scripts/fill_empty_repo.sh
 create mode 100755 scripts/fix_files.py
 create mode 100755 scripts/quasi.py
 create mode 100755 scripts/reject_files.py
 create mode 100755 scripts/remove_files.py
 create mode 100755 scripts/stats_atlas.py

diff --git a/README b/README
new file mode 100644
index 0000000..2a094ca
--- /dev/null
+++ b/README
@@ -0,0 +1,243 @@
+Instrukcja instalacji systemu zarządzania plikami.
+
+
+W poniższej instrukcji zakładamy, że czytelnik posiada podstawową
+wiedzę na temat administracji serwera svn. Zakładamy, że
+po stronie serwera zainstalowane są następujące narzędzia:
+
+* subversion
+* python (2.6 lub 2.7)
+* pysvn
+
+GUI ułatwiające pobieranie i wysyłanie zaanotowanych plików
+zbudowane jest na bazie:
+
+* python
+* wxPython
+* wxGlade
+
+Przygotowana została binarna wersja GUI pod system Windows,
+dzięki czemu użytkownicy systemu nie muszą posiadać
+zainstalowanych powyższych narzedzi.
+
+
+------------------------------------------------------------------
+
+
+Razem z instrukcją README administrator systemu powinien otrzymać:
+
+* Moduł dfs -- implementację systemu zarządzania plikami,
+* Katalog scripts -- zestaw skryptów ułatwiających tworzenie i
+  zarządzanie systemem,
+* Moduł gui -- implementację GUI dla użytkowników systemu.
+* Katalog z przykładową konfiguracją serwera
+  (pliki $SERVER.cfg oraz $USERS.cfg),
+* Katalog z przykładową konfiguracją klienta GUI (plik $GUI.cfg).
+
+Ponadto, administrator powinien wygenerować klucze szyfrujące
+połączenie klient-serwer -- pliki key.pem i cert.pem.
+Odpowiednie informacje na temat generowania kluczy można znaleźć
+pod adresem:
+
+http://docs.python.org/dev/library/ssl.html#certificates
+
+
+==================================================================
+
+
+1) Zakładanie nowego repozytorium.
+
+
+Na serwerze, w katalogu, w którym ma znajdować się repozytorium,
+należy wykonać komendę:
+
+$ svnadmin create $REPO.PATH
+
+W katalogu konfiguracyjnym repozytorium ($REPO.PATH/conf/) należy
+zmodyfikować dwa pliki:
+
+i) svnserve.conf
+
+...
+### and "none".  The sample settings below are the defaults.
+anon-access = none
+auth-access = write
+### The password-db option controls the location of the password
+...
+### Uncomment the line below to use the default password file.
+password-db = passwd
+### The authz-db option controls the location of the authorization
+...
+
+ii) passwd
+
+W pliku passwd należy wpisać login i hasło, które wykorzystywane
+będą po stronie serwera do zatwierdzania (commit-owania) zmian
+w repozytorium, np.:
+
+...
+machine = 95mnf83m-SDT%@vf
+...
+
+Ten sam login i hasło należy podać w konfiguracji serwera systemu
+zarządzania plikami, o czym w punkcie 2). W pliku passwd można
+podać również hasła dla innych użytkowników repozytorium -- więcej
+na ten temat można znaleźć w dokumentacji subversion.
+
+Hasła użytkowników systemu (anotatorów) również uzupełniane są w
+konfiguracji systemu zarządzania plikami opisanej w 2).
+
+
+------------------------------------------------------------------
+
+
+Na serwerze, w innym katalogu niż zostało założone repozytorium,
+należy utworzyć kopię roboczą repozytorium, z której korzystać
+będzie system zarządzania plikami. W tym celu należy wpisać:
+
+$ svn co file://$REPO.PATH $COPY.PATH
+Pobrano wersję 0.
+
+Gdzie $REPO.PATH to ścieżka repozytorium, a $COPY.PATH to
+docelowy katalog kopii roboczej.
+
+
+UWAGA: Zamiast ścieżki postaci file://$REPO.PATH, można również
+podać adres w postaci svn://$REPO.ADDR -- adres, który jest
+dostępny dla użytkowników repozytorium pracujących poza
+serwerem. Ponadto, kopię roboczą można założyć na innym
+serwerze niż repozytorium. Kopia robocza musi jednak znajdować
+się na tej samej maszynie, na której będzie później uruchamiany
+serwer systemu zarządzania plikami. Wadą tego rozwiązania
+może być wolniejsze działanie systemu.
+
+
+Pustą kopię roboczą należy na wstępie wypełnić strukturą katalogów
+dla systemu zarządzania plikami. Służy do tego skrypt
+fill_empty_repo.sh znajdujący się w katalogu scripts dostarczonym
+razem z tą instrukcją. Skrypt wywołuje się w następujący sposób:
+
+$ ./fill_empty_repo.sh $COPY.PATH
+
+Struktura katalogów powinna wyglądać następująco:
+
+$COPY.PATH/
+  new		<- katalog z nowymi plikami
+  annotation/	<- wyniki anotacji plików
+    A
+    B
+  adjudication	<- wyniki super-anotacji plików
+  db.xml	<- baza danych z bierzącymi informacjami
+		   dotyczącymi anotacji
+
+Na koniec należy watwierdzić zmiany wykonane w kopii roboczej:
+
+$ svn add $COPY.PATH/*
+$ svn commit --username=machine --password=95mnf83m-SDT%@vf -m "struktura"
+
+
+UWAGA: W katalogach annotation/A, annotation/B oraz adjudication
+znajdują się wysłane przez anotatorów na serwer pliki. Nie oznacza
+to jednak, że anotacja tych plików przez ich posiadaczy zakończyła
+się. Taką informację można uzyskać na podstawie bazy danych
+(pliki, których anotacja/super-anotacja zakończyła się, będą
+uzupełnione o element <checkinDate> w bazie danych).
+
+
+==================================================================
+
+
+2) Konfiguracja systemu zarządzania plikami.
+
+
+Konfiguracja systemu odbywa się w trzech plikach:
+* $SERVER.cfg 
+* $USERS.cfg
+* $PASSWD
+
+Razem z instrukcją i implementacją serwera powinny zostać
+dostarczone również przykładowe pliki konfiguracyjne, w których
+można znaleźć dokładniejsze informacje na temat konfiguracji
+serwera.
+
+i) $SERVER.cfg
+
+Plik $SERVER.cfg jest głównym plikiem konfiguracyjnym i jedynym
+wczytywanym bezpośrednio przez serwer systemu (znajdują się
+w nim również ścieżki do pozostałych dwóch plików konfiguracyjnych
+-- $USERS.cfg i $PASSWD).
+
+ii) $USERS.cfg
+
+Plik zawiera informacje o uprawnieniach do super-anotacji oraz
+o limitach pobierania plików.
+
+ii) $PASSWD
+
+W pliku znajdują się hasła określone dla użytkowników systemu
+(anotatorów). Poszczególne linie tego pliku mają postać:
+login = hasło
+
+
+==================================================================
+
+
+3) Używanie serwera systemu zarzadznania plikami.
+
+
+Do uruchamiania serwera służy narzędzie server.py w module dfs.
+Serwer uruchamia się następującą komendą:
+
+$ python server.py $SERVER.cfg
+
+Ewentualne błędy będą wypisywane na standardowe wyjście.
+
+
+------------------------------------------------------------------
+
+
+Karmienie systemu nowymi plikami przeznaczonymi do anotacji.
+
+Do tego celu służy skrypt add_files.py w katalogu scripts.
+
+$ python add_files.py $SERVER.cfg PATH1 PATH2 ...
+
+gdzie PATH1, PATH2, ... to ścieżki do plików, które mają zostać
+dodane do repozytorium. Zmiany nie są automatycznie commit-owane,
+więc trzeba użyć komendy svn commit do ich zatwierdzenia:
+
+UWAGA: Dodawanie plików, jak również jakiekolwiek prace
+administracyje w repozytorium systemu powinny być przeprowadzane
+przy wyłączonym serwerze server.py. Serwer można w sposób
+bezpieczny wyłączyć wysyłając sygnał SIGINT (np. Control-C)
+(Sygnał zostanie zignorowany, jeśli odbywa się komunikacja
+z serwerem).
+
+
+==================================================================
+
+
+4) Graficzny interfejs systemu zarządzania plikami.
+
+
+Implementacja GUI znajduje się w module gui dostarczonym
+razem z instrukcją. GUI przeznaczone jest dla użytkowników
+systemu, czyli anotatorów.
+
+
+------------------------------------------------------------------
+
+
+Konfiguracja GUI znajduje się w katalogu gui/data w pliku
+global.cfg. W tym samym katalogu powinien znajdować się
+wygenerowany przez administratora klucz publiczny (cert.pem).
+Dokładniejsze informacje na temat konfiguracji znajdują się w
+przykładowym pliku konfiguracyjnym gui/data/global.cfg.
+
+
+------------------------------------------------------------------
+
+
+GUI uruchamia się komendą:
+
+$ python manager.py
diff --git a/dfs/__init__.py b/dfs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dfs/__init__.py
diff --git a/dfs/config.py b/dfs/config.py
new file mode 100644
index 0000000..5a53f9c
--- /dev/null
+++ b/dfs/config.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+import re
+import os
+
+SECTION = re.compile("^\[(.*)\]$")
+SUBSECTION = re.compile("^\{(.*)\}$")
+ENTRY = re.compile("^([^=\s]+)\s*=\s*([^=]+)\s*$")
+
+def cut_comment(s):
+    return s.split("#")[0]
+
+class Config:
+
+    def __init__(self, path):
+        section = None
+        subsection = None
+        self.d = {}
+        with open(path) as f:
+            for line in f:
+                line = cut_comment(line).strip()
+
+                m = SECTION.search(line)
+                if m is not None:
+                    section = m.group(1)
+                    subsection = None
+                    continue
+
+                m = SUBSECTION.search(line)
+                if m is not None:
+                    subsection = m.group(1)
+                    continue
+
+                m = ENTRY.search(line)
+                if m is not None:
+                    key = m.group(1)
+                    val = m.group(2)
+                    if subsection == "paths":
+                        # print path, val, os.path.join(path, val)
+                        val = self.relative_path(val, path)
+                    if section is not None:
+                        self.d[section + "." + key] = val
+                    else:
+                        self.d[key] = val
+
+    def relative_path(self, path, ref):
+        return os.path.join(os.path.dirname(ref), path)
+
+    def __getitem__(self, key):
+        return self.d[key]
diff --git a/dfs/database.py b/dfs/database.py
new file mode 100644
index 0000000..4f4458b
--- /dev/null
+++ b/dfs/database.py
@@ -0,0 +1,381 @@
+# -*- coding: utf-8 -*-
+import time
+from datetime import date, datetime
+from collections import defaultdict
+
+from xml.etree.ElementTree import Element, ElementTree, tostring
+import xml.etree.ElementTree as ET
+
+class Database:
+
+    def __init__(self, db, anno_per_file, from_file=True):
+        if from_file:
+            self.db_name = db
+            self.tree = ET.parse(db)
+            self.root = self.tree.getroot()
+        else:
+            self.db_name = None
+            self.tree = None
+            self.root = ET.fromstring(db)
+            
+        self.anno_per_file = anno_per_file
+        self.make_index()
+        
+    def owned(self, ann_elem):
+        return not self.finished(ann_elem) and not self.returned(ann_elem)
+
+    def finished(self, ann_elem):
+        return ann_elem.find("checkinDate") is not None 
+    
+    def returned(self, ann_elem):
+        return ann_elem.find("return") is not None
+    
+    def get_reason(self, ann_elem):
+        reas = ann_elem.find("return").find("reason").text
+        if reas is None:
+            reas = ""
+        return reas
+        
+    def rejected(self, file_elem):
+        return file_elem.find("rejected") is not None
+    
+    def fixed(self, ann_elem):
+        ret = ann_elem.find("return")
+        if ret is not None and ret.find("fixed") is not None:
+            return True
+        return False
+       
+    def ann_elem_for_user(self, file_elem, user):
+        idx = 0
+        for ann_elem in file_elem.findall("ann"):
+            if ann_elem.find("annName").text == user:                                
+                return ann_elem, idx
+            idx = idx + 1
+        return None, None
+    
+    def sann_elem_for_user(self, file_elem, user):
+        for ann_elem in file_elem.findall("s_ann"):
+            if ann_elem.find("annName").text == user:                                
+                return ann_elem        
+        return None
+    
+    def fixed_for_user(self, file_elem, user, is_adj):
+        if is_adj:
+            ann = self.sann_elem_for_user(file_elem, user)
+        else:
+            ann, idx = self.ann_elem_for_user(file_elem, user)
+        if ann is not None:
+            return self.fixed(ann)        
+        return False        
+    
+    def make_index(self):
+        self.file_index = {}
+        self.ann_owned_index = defaultdict(list)
+        self.adj_owned_index = defaultdict(list)
+
+        for file_elem in self.root:
+            name_elem = file_elem.find("name")
+            if name_elem == None:
+                raise Exception("No name assigned to a file element !")
+            self.file_index[name_elem.text] = file_elem
+            
+            if self.rejected(file_elem):
+                continue
+
+            for ann_elem in file_elem.findall("ann"):
+                if self.owned(ann_elem): 
+                    ann_name = ann_elem.find("annName").text
+                    self.ann_owned_index[ann_name].append(file_elem)
+
+            for ann_elem in file_elem.findall("s_ann"):
+                if self.owned(ann_elem):
+                    ann_name = ann_elem.find("annName").text
+                    self.adj_owned_index[ann_name].append(file_elem)
+
+    def fix(self, ann_elem):
+        if not self.returned(ann_elem):
+            raise Exception("File not returned in database!")
+        if self.fixed(ann_elem):
+            raise Exception("File already fixed in database!")
+        
+        ret = ann_elem.find("return")
+        fixed = ET.SubElement(ret, "fixed")
+    
+    def add(self, file_name):
+        if self.file_index.has_key(file_name):
+            raise Exception("File name already in database !")
+        file_elem = ET.SubElement(self.root, "file")
+        name_elem = ET.SubElement(file_elem, "name")
+        name_elem.text = file_name
+        date_elem = ET.SubElement(file_elem, "addDate")
+        date_elem.text = time.strftime("%c")
+        self.file_index[file_name] = file_elem
+        
+    def remove(self, file_name):
+        if not self.file_index.has_key(file_name):
+            raise Exception("File name not in database !")
+        file_elem = self.file_index[file_name]
+        self.root.remove(file_elem)
+        self.make_index()
+        
+    def reject(self, file_name, reason):
+        if not self.file_index.has_key(file_name):
+            raise Exception("File name not in database !")
+        file_elem = self.file_index[file_name]
+        if self.rejected(file_elem):
+            raise Exception("File already rejected !")
+        rej_elem = ET.SubElement(file_elem, "rejected")
+        rej_elem.text = reason        
+        self.make_index()
+
+    # For prettyprint, used in 'save' method.
+    def indent(self, elem, level=0):
+        i = "\n" + level * "  "
+        if len(elem):
+            if not elem.text or not elem.text.strip():
+                elem.text = i + "  "
+            if not elem.tail or not elem.tail.strip():
+                elem.tail = i
+            for elem in elem:
+                self.indent(elem, level + 1)
+            if not elem.tail or not elem.tail.strip():
+                elem.tail = i
+        else:
+            if level and (not elem.tail or not elem.tail.strip()):
+                elem.tail = i
+
+    def save(self):
+    	if not self.db_name:
+    	    raise Exception("XML not read from file !")
+    
+    	self.indent(self.root)
+    	self.tree.write(self.db_name, encoding="utf-8")
+
+    def upload_prevention(self, file_name, user, is_super):
+        if not self.file_index.has_key(file_name):
+            return "Filename " + file_name.encode('utf-8') + " not known by server."
+        file_elem = self.file_index[file_name]
+        if self.rejected(file_elem):
+            return "File rejected: " + file_name.encode('utf-8')
+        
+        if is_super:                
+            ann_elem = file_elem.find("s_ann")
+            if ann_elem is None:
+                return "No superannotator assigned to file " + file_name.encode('utf-8')
+            
+            if user != ann_elem.find("annName").text:
+                return "Different superannotator assigned to file " + file_name.encode('utf-8')
+            
+            if ann_elem.find("checkinDate") is not None:
+                return "File " + file_name.encode('utf-8') + " already checked in!"
+            
+            return None
+                        
+        else:            
+            annotations = file_elem.findall("ann")
+            owners = map(lambda ann: ann.find("annName").text, annotations)
+            if not user in owners:
+                return "User " + user + " doesn't own the file " + file_name.encode('utf-8')
+            
+            i = owners.index(user)
+            ann_elem = annotations[i]
+            if ann_elem.find("checkinDate") is not None:
+                return "User " + user + " already checked in the file " + file_name.encode('utf-8')
+            
+            return None
+            
+    def upload_dest(self, file_name, user):
+        """Upload destination (annName element and ID -- 1 or 2) directory."""
+        file_elem = self.file_index[file_name]
+        annotations = file_elem.findall("ann")
+
+        owners = map(lambda ann: ann.find("annName").text, annotations)
+        exc = Exception("Cannot upload " + file_name.encode('utf-8')
+                       + " file by " + user + " annotator !")
+        if not user in owners:
+            raise exc
+
+        i = owners.index(user)
+        ann_elem = annotations[i]
+        if ann_elem.find("checkinDate") is not None:
+            raise exc
+
+        return (ann_elem, i)
+
+    def upload_id(self, file_name, user):
+        return self.upload_dest(file_name, user)[1]
+
+    def return_file(self, file_name, user, reason):        
+        (ann_elem, i) = self.upload_dest(file_name, user)
+        ret_elem = ET.SubElement(ann_elem, "return")        
+        date_elem = ET.SubElement(ret_elem, "date")        
+        date_elem.text = time.strftime("%c")
+        date_elem = ET.SubElement(ret_elem, "reason")
+        if reason is None:
+            reason = ""
+        date_elem.text = reason
+        return i
+    
+    def return_file_prim(self, file_name, user, reason):        
+        file_elem = self.file_index[file_name]
+        ann_elem = file_elem.find("s_ann")
+
+        if (user != ann_elem.find("annName").text or
+             ann_elem.find("checkinDate") is not None):
+            raise Exception("Cannot upload " + file_name.encode('utf-8')
+                    + " file by " + user + " annotator !")
+
+        ret_elem = ET.SubElement(ann_elem, "return")        
+        date_elem = ET.SubElement(ret_elem, "date")        
+        date_elem.text = time.strftime("%c")
+        date_elem = ET.SubElement(ret_elem, "reason")
+        if reason is None:
+            reason = ""
+        date_elem.text = reason
+        
+    def upload(self, file_name, user):
+        (ann_elem, i) = self.upload_dest(file_name, user)
+        date_elem = ET.SubElement(ann_elem, "checkinDate")
+        date_elem.text = time.strftime("%c")
+        return i
+
+    def upload_prim(self, file_name, user):
+        file_elem = self.file_index[file_name]
+        ann_elem = file_elem.find("s_ann")
+
+        if (user != ann_elem.find("annName").text or self.finished(ann_elem)):
+            raise Exception("Cannot upload " + file_name.encode('utf-8')
+                    + " file by " + user + " annotator !")
+
+        date_elem = ET.SubElement(ann_elem, "checkinDate")
+        date_elem.text = time.strftime("%c")
+
+    def download(self, file_name, user):
+        file_elem = self.file_index[file_name]
+
+        annotations = file_elem.findall("ann")
+        owners = map(lambda ann: ann.find("annName").text, annotations)
+        
+        if self.fixed_for_user(file_elem, user, False):
+            ann, idx = self.ann_elem_for_user(file_elem, user)
+            date = ann.find("return").find("date").text
+            ann.remove(ann.find("return"))
+            
+            date_elem = ET.SubElement(ann, "returnDate")
+            date_elem.text = date
+                     
+            date_elem = ET.SubElement(ann, "checkoutDate")
+            date_elem.text = time.strftime("%c")
+            
+            return idx
+        
+        else:        
+            if user in owners or len(owners) + 1 > self.anno_per_file:
+                raise Exception("Cannot set " + user + " annotator to '"
+                                    + file_name.encode('utf-8') + "' file !")
+        
+            ann_elem = ET.SubElement(file_elem, "ann")
+            ann_name_elem = ET.SubElement(ann_elem, "annName")
+            ann_name_elem.text = user
+            
+            date_elem = ET.SubElement(ann_elem, "checkoutDate")
+            date_elem.text = time.strftime("%c")
+            
+            return None
+
+    def download_prim(self, file_name, user):
+        file_elem = self.file_index[file_name]
+
+        if self.fixed_for_user(file_elem, user, True):
+            sann = self.sann_elem_for_user(file_elem, user)
+            date = sann.find("return").find("date").text
+            sann.remove(sann.find("return"))
+            
+            date_elem = ET.SubElement(sann, "returnDate")
+            date_elem.text = date
+                       
+            date_elem = ET.SubElement(sann, "checkoutDate")
+            date_elem.text = time.strftime("%c")    
+            
+            return True
+                   
+        else:  
+            if len(file_elem.findall("s_ann")) > 0:
+                raise Exception("Cannot set " + user + " adjudicator to '"
+                        + file_name.encode('utf-8') + "' file !")
+    
+            ann_elem = ET.SubElement(file_elem, "s_ann")
+            ann_name_elem = ET.SubElement(ann_elem, "annName")
+            ann_name_elem.text = user
+            
+            date_elem = ET.SubElement(ann_elem, "checkoutDate")
+            date_elem.text = time.strftime("%c")
+            
+            return False
+
+    def for_annotation(self, user):
+        priority = []
+        result = []    
+        for file_elem in self.root:
+            if self.rejected(file_elem) or file_elem.find("s_ann") is not None:
+                continue
+            
+            anns = file_elem.findall("ann")
+            owners = map(lambda ann: ann.find("annName").text, anns)
+            if len(owners) < self.anno_per_file and user not in owners:
+                result.append(file_elem.find("name").text)
+            
+            if self.fixed_for_user(file_elem, user, False):
+                priority.append(file_elem.find("name").text)                
+                
+        return priority, result
+
+    def for_adjudication(self, user):
+        priority = []        
+        result = []
+        for file_elem in self.root:
+            if self.rejected(file_elem):
+                continue
+            
+            anns = file_elem.findall("ann")
+            owners = map(lambda ann: ann.find("annName").text, anns)
+            checked = map(lambda ann: ann.find("checkinDate") != None, anns)
+            if (len(owners) == self.anno_per_file
+                and file_elem.find("s_ann") is None
+                and user not in owners
+                and all(checked)):
+                result.append(file_elem.find("name").text)
+                
+            if self.fixed_for_user(file_elem, user, True):                    
+                priority.append(file_elem.find("name").text)                
+                
+        return priority, result
+
+    def owns_normal(self, user): 
+        return [ file_elem.find("name").text
+                 for file_elem
+                 in self.ann_owned_index[user] ]
+
+    def owns_super(self, user):
+        return [ file_elem.find("name").text
+                 for file_elem
+                 in self.adj_owned_index[user] ]
+
+    def owns(self, user):
+        return self.owns_normal(user) + self.owns_super(user)
+    
+    def finished_count(self, user):
+        n = 0
+        for file_elem in self.root:
+                 
+            if self.rejected(file_elem):
+                continue
+            
+            items = file_elem.findall("ann")
+            items.extend(file_elem.findall("s_ann"))
+            for ann_elem in items:
+                ann_name = ann_elem.find("annName").text
+                if ann_name == user and self.finished(ann_elem) and not self.returned(ann_elem):                
+                    n += 1
+                                        
+        return n
diff --git a/dfs/msg/__init__.py b/dfs/msg/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dfs/msg/__init__.py
diff --git a/dfs/msg/message.py b/dfs/msg/message.py
new file mode 100644
index 0000000..295fb11
--- /dev/null
+++ b/dfs/msg/message.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+__all__ = ["decode", "Message", "OkMessage", "KoMessage", "DownloadRequest"
+        , "UploadRequest", "DownloadPrimRequest", "ClientDone", "ServerDone"
+        , "NumMessage", "BoolMessage", "ReturnRequest", "CheckoutRequest",
+        "CheckinRequest", "StatsRequest"]
+
+
+class Message(object):
+
+    """
+    Base class for SSL communication messages.
+    """
+
+    def __init__(self, contents=""):
+        self.contents = contents
+
+    def get_contents(self):
+        return self.contents
+
+    @classmethod
+    def from_contents(cls, contents):
+        return cls(contents)
+
+    def encode(self):
+        """
+        Prepare message to be sent over network.
+        WARNING: Do not override this method.
+        """
+        # Add "-" on the beggining to prevent
+        # message contents from being void.
+        return self.msg_type, "-" + self.get_contents()
+
+
+class NumMessage(Message):
+
+    def __init__(self, n):
+        self.number = n
+
+    def get_contents(self):
+        return str(self.number)
+
+    @classmethod
+    def from_contents(cls, contents):
+        return cls(int(contents))
+
+    def get_number(self):
+        return self.number
+
+
+class BoolMessage(Message):
+
+    def __init__(self, b):
+        self.b = b
+
+    def get_contents(self):
+        return "1" if self.b else "0"
+
+    @classmethod
+    def from_contents(cls, contents):
+        return cls(bool(int(contents)))
+
+    def get_boolean(self):
+        return self.b
+
+class ReturnRequest(NumMessage):
+    pass
+
+class CheckoutRequest(Message):
+    pass
+
+class CheckinRequest(NumMessage):
+    pass
+
+class DownloadRequest(NumMessage):
+    pass
+
+class DownloadPrimRequest(NumMessage):
+    pass
+
+class UploadRequest(NumMessage):
+    pass
+
+class OkMessage(Message):
+    pass
+
+class KoMessage(Message):
+    pass
+
+class StatsRequest(Message):
+    pass
+
+class ServerDone(Message):
+    pass
+
+class ClientDone(Message):
+    pass
+
+
+def subclasses(cls):
+    yield cls
+    for child in cls.__subclasses__():
+        for desc in subclasses(child):
+            yield desc
+
+def decode(msg_type, contents):
+    for cls in subclasses(Message):
+        if cls.msg_type == msg_type:
+            # Discard first, dummy character;
+            # Confront Message.encode method.
+            return cls.from_contents(contents[1:])
+
+for i, cls in enumerate(subclasses(Message)):
+    cls.msg_type = i
diff --git a/dfs/msg/stream.py b/dfs/msg/stream.py
new file mode 100644
index 0000000..116bab0
--- /dev/null
+++ b/dfs/msg/stream.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+import struct
+
+from message import decode
+
+__all__ = ["read_msg", "write_msg", "EndOfStream"]
+
+class EndOfStream(Exception):
+    pass
+
+class BadMessage(Exception):
+    pass
+
+def read_msg(connstream):
+    encoded = read_encoded(connstream)
+    return decode(*encoded)
+
+def accept_msg(connstream, cls):
+    msg = read_msg(connstream)
+    if type(msg) != cls:
+        raise BadMessage("Expected %s, got %s" %
+                (cls.__name__, type(msg).__name__))
+    return msg
+
+def write_msg(connstream, msg):
+    encoded = msg.encode()
+    write_encoded(connstream, *encoded)
+
+def read_encoded(connstream):
+    _type = read_type(connstream)
+    length = read_length(connstream)
+    content = read_content(connstream, length)
+    return _type, content
+
+def write_encoded(connstream, _type, content):
+    write_type(connstream, _type)
+    write_length(connstream, len(content))
+    write_content(connstream, content)
+
+def read_type(connstream):
+    data = read_all(connstream, 1)
+    return struct.unpack("!B", data)[0]
+
+def write_type(connstream, _type):
+    data = struct.pack("!B", _type)
+    write_all(connstream, data)
+
+def read_length(connstream):
+    data = read_all(connstream, 4)
+    return struct.unpack("!L", data)[0]
+
+def write_length(connstream, n):
+    data = struct.pack("!L", n)
+    write_all(connstream, data)
+
+def read_content(connstream, length):
+    return read_all(connstream, length)
+
+def write_content(connstream, content):
+    write_all(connstream, content)
+
+def write_all(connstream, data):
+    k = connstream.write(data)
+    if k != len(data):
+        raise Exception("Did not send all data !")
+
+def read_all(connstream, k):
+    data = ""
+    while k > 0:
+        # part = connstream.read()
+        part = connstream.read(min(k, 1024))
+        if len(part) == 0:
+            raise EndOfStream
+        k -= len(part)
+        # if k < 0:
+        #     raise Exception("Received more than expected !")
+        data += part
+    return data
+
diff --git a/dfs/repo.py b/dfs/repo.py
new file mode 100644
index 0000000..9729984
--- /dev/null
+++ b/dfs/repo.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+import os
+import shutil
+import pysvn
+
+class Repo:
+
+    def __init__(self, path, login, passwd):
+        self.path = path
+        def get_login(_realm, _username, _may_save):
+            return True, login, passwd, False
+        self.svn = pysvn.Client()        
+        self.svn.callback_get_login = get_login
+        self.svn.update(self.path)
+        
+    def add(self, src_path):
+        part_name = os.path.basename(src_path)
+        dest_path = os.path.join(self.new_path(), part_name)
+        
+        if (not self.svn.info(dest_path) is None) or (os.path.exists(dest_path)):
+            raise Exception(src_path+" already present in repository.")
+        
+        shutil.copy(src_path, dest_path)
+        self.svn.add(dest_path)        
+        
+    def remove(self, filename, anno_per_file):
+        paths = set()        
+        paths.add(os.path.join(self.new_path(), filename))
+        for i in range(anno_per_file):
+            paths.add(self.upload_path(filename, i))
+        paths.add(os.path.join(self.finito_path(), filename))
+        
+        removed = []
+        for path in paths:
+            if os.path.exists(path):
+                self.svn.remove(path)
+                removed.append(path)
+                
+        return removed
+
+    def db_path(self):
+        return os.path.join(self.path, "db.xml")
+
+    def new_path(self):
+        return os.path.join(self.path, "new")
+
+    def ann_path(self, idx):
+        ids = chr(idx+ord('A'))
+        tail = os.path.join("annotation", ids)
+        return os.path.join(self.path, tail)
+
+    def finito_path(self):
+        return os.path.join(self.path, "adjudication")
+
+    def upload_path(self, file_name, idx):
+        return os.path.join(self.ann_path(idx), file_name)
+
+    def upload_prim_path(self, file_name):
+        return os.path.join(self.finito_path(), file_name)
+        
+    def read_data(self, path):
+        with open(path) as src:
+            return src.read()
+
+    def read_contents(self, path, exts):
+        contents = {}
+        for ext in exts:
+            contents[ext] = self.read_data(path + ext)
+        return contents
+
+    def write_data(self, path, data):
+        with open(path, "w") as dest:
+            dest.write(data)
+
+    def write_contents(self, path, contents):
+        for ext, data in contents.iteritems():
+            dest_path = path + ext
+            self.write_data(dest_path, data)
+            if self.svn.info(dest_path) is None:
+                self.svn.add(dest_path)
+
+    def upload(self, file_id, idx, contents):
+        self.write_contents(self.upload_path(file_id, idx), contents)
+
+    def upload_prim(self, file_id, contents):        
+        self.write_contents(self.upload_prim_path(file_id), contents)
+
+    def download(self, file_id, exts):
+        return self.read_contents(os.path.join(self.new_path(), file_id), exts)
+
+    def download_prim(self, file_id, exts, anno_per_file):
+        result = []
+        for i in range(anno_per_file):
+            conts = self.read_contents(os.path.join(self.ann_path(i), file_id), exts)
+            result.append(conts)            
+        return result
+
+    def checkout(self, file_id, idx, exts):
+        src_paths = [ self.upload_path(file_id, idx) 
+                    , os.path.join(self.new_path(), file_id) ]
+        for path in src_paths:
+            if all(os.path.exists(path + ext) for ext in exts):
+                return self.read_contents(path, exts)
+ 
+    def checkout_prim(self, file_id, exts):
+        path = os.path.join(self.finito_path(), file_id)
+        if all(os.path.exists(path + ext) for ext in exts):
+            return self.read_contents(path, exts)
+
+    def commit(self, message):
+        self.svn.checkin(self.path, message, recurse=True)
+
+    def revert(self):
+        self.svn.revert(self.path, recurse=True)
diff --git a/dfs/server.py b/dfs/server.py
new file mode 100644
index 0000000..639e5ec
--- /dev/null
+++ b/dfs/server.py
@@ -0,0 +1,401 @@
+# -*- coding: utf-8 -*-
+import sys
+import os
+import traceback
+import socket, ssl
+import random
+from optparse import OptionParser 
+import signal
+
+from utils import validate_user, UserInvalid, send_contents, receive_contents
+from msg.stream import read_msg, write_msg, accept_msg, EndOfStream, BadMessage
+from msg.message import *
+from repo import Repo
+from database import Database
+from config import Config
+
+class SSLServer:
+
+    def __init__(self, host, port, backlog, cert_file, key_file,
+            repository, svn_login, svn_passwd, pass_file,
+            users_file, file_exts, anno_per_file, log=None):
+        """
+        Initialize SSL server.
+
+        params:
+        =======
+        host : str
+            Host name.
+        port : int
+            Port number.
+        backlog : int
+            Maximum number of waiting connections.
+        cert_file : path
+            Public PEM certificate.
+        key_file : path
+            Private key.
+        pass_file : path
+            File with client passwords.
+        users_file : path
+            File with additional users configuration.
+        repository : path
+            Subversion working copy.
+        svn_login : str
+            Subversion login.
+        svn_passwd : str
+            Subversion password.
+        file_exts : [str]
+            List of file extensions.
+        anno_per_file : int
+            Number of normal annotators per file (1 or 2)
+        """
+        self.cert_file = cert_file
+        self.key_file = key_file
+        self.pass_file = pass_file
+        self.users_file = users_file
+        self.wc = Repo(repository, svn_login, svn_passwd)
+        self.file_exts = file_exts
+        if log is not None:
+            self.log = open(log, "a")
+        else:
+            self.log = sys.stdout
+        self.bound = bind_socket(host, port, backlog=backlog)
+        self.serving = None
+        self.anno_per_file = anno_per_file
+
+    def run(self):
+        while True:
+            newsocket, fromaddr = self.bound.accept()
+            self.serving = fromaddr
+            connstream = None
+            try:
+                connstream = ssl.wrap_socket(newsocket,
+                                         server_side=True,
+                                         certfile=self.cert_file,
+                                         keyfile=self.key_file,
+                                         ssl_version=ssl.PROTOCOL_TLSv1)
+            
+                login = validate_user(connstream, self.pass_file)
+                self.serve_client(connstream, login)
+            except UserInvalid as login:
+                print >> self.log, "AUTH ERROR:", login
+            except BadMessage as info:
+                print >> self.log, "BAD MESSAGE ERROR:", info
+                print >> self.log, traceback.format_exc().strip()
+            except EndOfStream:
+                print >> self.log, "UNEXPECTED END OF STREAM:"
+                print >> self.log, traceback.format_exc().strip()
+            except:
+                print >> self.log, "UNEXPECTED ERROR:"
+                print >> self.log, traceback.format_exc().strip()
+            finally:
+                try:
+                    if not connstream is None:
+                        connstream.shutdown(socket.SHUT_RDWR)
+                except:
+                    print >> self.log, "UNEXPECTED ERROR:"
+                    print >> self.log, traceback.format_exc().strip()
+                finally:
+                    if not connstream is None:
+                        connstream.close()
+                    self.serving = None
+
+    def exit(self):
+        if self.serving is not None:
+            print ("Client from %s connected to the server"
+                  % str(self.serving))
+        else:
+            sys.exit(0)
+
+    def process_msg(self, msg, stream, login):
+        if type(msg) == UploadRequest:
+            self.process_upload_msg(msg, stream, login, checkin=False)
+        elif type(msg) == CheckinRequest:
+            self.process_upload_msg(msg, stream, login, checkin=True)
+        elif type(msg) == CheckoutRequest:
+            self.process_checkout_msg(msg, stream, login)
+        elif type(msg) == DownloadRequest:
+            self.process_download_msg(msg, stream, login)
+        elif type(msg) == DownloadPrimRequest:
+            self.process_download_prim_msg(msg, stream, login)
+        elif type(msg) == ReturnRequest:
+            self.process_return_msg(msg, stream, login)
+        elif type(msg) == StatsRequest:
+            self.process_stats_msg(msg, stream, login)      
+        else:
+            print >> self.log, login, "sent:", msg
+
+    def process_return_msg(self, msg, stream, login):
+        reason = accept_msg(stream, Message).get_contents().decode("utf-8")
+        
+        usr_cfg = Config(self.users_file)
+        try:
+            user_adj = login in usr_cfg["auth.adjudicators"].split()
+        except KeyError:
+            user_adj = False
+
+        db = Database(self.wc.db_path(), self.anno_per_file)
+        n = msg.get_number()
+        for _ in range(n):
+            file_id = accept_msg(stream, Message).get_contents()        
+            is_adj = accept_msg(stream, BoolMessage).get_boolean()
+            if is_adj and not user_adj:
+                write_msg(stream, KoMessage())
+                print >> self.log, "Upload error - user " + login + " tried to upload super annotated file " + file_id + " but is not a super annotator"
+                continue
+            else:
+                error = db.upload_prevention(file_id, login, is_adj)
+                if error is not None:
+                    write_msg(stream, KoMessage())
+                    print >> self.log, "Upload error by user " + login + ". Details: "+error                    
+                    continue                    
+            
+            write_msg(stream, OkMessage())            
+            contents = receive_contents(stream)
+            for key in contents.keys():
+                if key not in self.file_exts:
+                    print >> self.log, "Skipped file uploaded by user " + login + ". Filename:"+file_id+key
+                    del contents[key]
+            
+            if is_adj is False:
+                idx = db.return_file(file_id, login, reason)                
+                self.wc.upload(file_id, idx, contents)
+            else:
+                db.return_file_prim(file_id, login, reason)
+                self.wc.upload_prim(file_id, contents)
+
+        db.save()
+        accept_msg(stream, ClientDone)
+        self.wc.commit("return request from %s"
+                      % (login))
+        write_msg(stream, ServerDone())        
+        
+        
+    def process_upload_msg(self, msg, stream, login, checkin=False):
+        usr_cfg = Config(self.users_file)
+        try:
+            user_adj = login in usr_cfg["auth.adjudicators"].split()
+        except KeyError:
+            user_adj = False
+
+        db = Database(self.wc.db_path(), self.anno_per_file)
+        n = msg.get_number()
+        for _ in range(n):
+            file_id = accept_msg(stream, Message).get_contents()
+            is_adj = accept_msg(stream, BoolMessage).get_boolean()
+            if is_adj and not user_adj:
+                write_msg(stream, KoMessage())
+                print >> self.log, "Upload error - user " + login + " tried to upload super annotated file " + file_id + " but is not a super annotator"
+                continue
+            else:
+                error = db.upload_prevention(file_id, login, is_adj)
+                if error is not None:
+                    write_msg(stream, KoMessage())
+                    print >> self.log, "Upload error by user " + login + ". Details: "+error                    
+                    continue                    
+            
+            write_msg(stream, OkMessage())            
+            contents = receive_contents(stream)
+            for key in contents.keys():
+                if key not in self.file_exts:
+                    print >> self.log, "Skipped file uploaded by user " + login + ". Filename:"+file_id+key
+                    del contents[key]
+
+            if is_adj is False:
+                if checkin:
+                    idx = db.upload(file_id, login)
+                else:
+                    idx = db.upload_id(file_id, login)
+                self.wc.upload(file_id, idx, contents)
+            else:
+                if checkin:
+                    db.upload_prim(file_id, login)
+                self.wc.upload_prim(file_id, contents)
+
+        if checkin:
+            db.save()
+        accept_msg(stream, ClientDone)
+        self.wc.commit("%s request from %s"
+                      % ("checkin" if checkin else "upload", login))
+        write_msg(stream, ServerDone())
+
+    def process_checkout_msg(self, msg, stream, login):
+        db = Database(self.wc.db_path(), self.anno_per_file)
+
+        ann_files = db.owns_normal(login)
+        write_msg(stream, NumMessage(len(ann_files)))
+        for file_id in ann_files:
+            write_msg(stream, Message(file_id))
+            idx = db.upload_id(file_id, login)
+            contents = self.wc.checkout(file_id, idx, self.file_exts)
+            send_contents(stream, contents)
+
+        adj_files = db.owns_super(login)
+        write_msg(stream, NumMessage(len(adj_files)))
+        write_msg(stream, NumMessage(self.anno_per_file))
+        for file_id in adj_files:
+            write_msg(stream, Message(file_id))
+
+            contents = self.wc.download_prim(file_id, self.file_exts, self.anno_per_file)
+            for conts in contents:
+                send_contents(stream, conts)
+
+            conts3 = self.wc.checkout_prim(file_id, self.file_exts)
+            if conts3 is None:
+                write_msg(stream, BoolMessage(False))
+            else:
+                write_msg(stream, BoolMessage(True))
+                send_contents(stream, conts3)
+
+        accept_msg(stream, ClientDone)
+        #self.wc.commit("checkout request from %s" % login)
+        write_msg(stream, ServerDone())
+
+    def process_stats_msg(self, msg, stream, login):
+        usr_cfg = Config(self.users_file)
+        db = Database(self.wc.db_path(), self.anno_per_file)
+                
+        write_msg(stream, NumMessage(db.finished_count(login)))
+        
+        accept_msg(stream, ClientDone)        
+        write_msg(stream, ServerDone())
+        
+    def process_download_msg(self, msg, stream, login):
+        usr_cfg = Config(self.users_file)
+        db = Database(self.wc.db_path(), self.anno_per_file)
+
+        down_num = msg.get_number()
+        owns_num = len(db.owns_normal(login))
+        limit = float('inf')
+        try:
+            limit = int(usr_cfg["limits.%s.annotation" % login])
+        except KeyError:
+            limit = int(usr_cfg["limits.annotation"])
+        if down_num + owns_num > limit:
+            down_num = max(0, limit - owns_num)
+
+        for_ann_fixed, for_ann = db.for_annotation(login)
+        if len(for_ann) + len(for_ann_fixed) == 0:
+            print >> self.log, "User " + login + " has no more files to download for annotation."
+        
+        down_files = sample(for_ann_fixed, down_num)
+        down_num = max(0, down_num - len(down_files))
+        down_files = down_files + sample(for_ann, down_num)
+        
+        write_msg(stream, NumMessage(len(down_files)))
+        
+        for file_id in down_files:
+            fixed_idx = db.download(file_id, login)
+            write_msg(stream, Message(file_id))
+            if fixed_idx is None:
+                contents = self.wc.download(file_id, self.file_exts)
+            else:
+                contents = self.wc.checkout(file_id, fixed_idx, self.file_exts)
+            send_contents(stream, contents)
+        db.save()
+        accept_msg(stream, ClientDone)
+        self.wc.commit("download request from %s" % login)
+        write_msg(stream, ServerDone())
+
+    def process_download_prim_msg(self, msg, stream, login):
+        usr_cfg = Config(self.users_file)
+        try:
+            adjudicators = usr_cfg["auth.adjudicators"].split()
+        except KeyError:
+            adjudicators = []
+        if login in adjudicators:
+            write_msg(stream, OkMessage())
+        else:
+            write_msg(stream, KoMessage())
+            return
+        db = Database(self.wc.db_path(), self.anno_per_file)
+
+        down_num = msg.get_number()
+        owns_num = len(db.owns_super(login))
+        limit = float('inf')
+        try:
+            limit = int(usr_cfg["limits.%s.adjudication" % login])
+        except KeyError:
+            limit = int(usr_cfg["limits.adjudication"])
+        if down_num + owns_num > limit:
+            down_num = max(0, limit - owns_num)
+
+        for_adj_fixed, for_adj = db.for_adjudication(login)        
+        if len(for_adj) + len(for_adj_fixed) == 0:
+            print >> self.log, "User " + login + " has no more files to download for superannotation."
+
+        down_files = sample(for_adj_fixed, down_num)        
+        down_num = max(0, down_num - len(down_files))
+        down_files = down_files + sample(for_adj, down_num)
+        
+        write_msg(stream, NumMessage(len(down_files)))
+        write_msg(stream, NumMessage(self.anno_per_file))        
+        for file_id in down_files:            
+            write_msg(stream, Message(file_id))
+            fixed = db.download_prim(file_id, login)
+            
+            contents = self.wc.download_prim(file_id, self.file_exts, self.anno_per_file)
+            for conts in contents:
+                send_contents(stream, conts)
+                
+            if fixed:
+                write_msg(stream, BoolMessage(True))                
+                contents = self.wc.checkout_prim(file_id, self.file_exts)                
+                send_contents(stream, contents)
+            else:
+                write_msg(stream, BoolMessage(False))
+                            
+        db.save()
+        accept_msg(stream, ClientDone)
+        self.wc.commit("download' request from %s" % login)
+        write_msg(stream, ServerDone())
+
+    def serve_client(self, connstream, login):
+        msg = read_msg(connstream)
+        try:
+            self.process_msg(msg, connstream, login)
+        except:
+            self.wc.revert()
+            raise
+
+def bind_socket(host, port, backlog=5):
+    bound = socket.socket()
+    bound.bind((host, port))
+    bound.listen(backlog)
+    return bound
+
+def sample(population, n):
+    if len(population) > n:
+        return random.sample(population, n)
+    else:
+        return population
+
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog CONFIG""")
+    (options, args) = optparser.parse_args()
+    if len(args) != 1:
+        optparser.print_help()
+        sys.exit(0)
+
+    cfg = Config(args[0])
+    server = SSLServer(
+            host=cfg["connection.host"],
+            port=int(cfg["connection.port"]),
+            backlog=int(cfg["connection.backlog"]),
+            cert_file=cfg["connection.certfile"],
+            key_file=cfg["connection.keyfile"],
+            repository=cfg["svn.repository"],
+            svn_login=cfg["svn.login"],
+            svn_passwd=cfg["svn.passwd"],
+            pass_file=cfg["users.passfile"],
+            users_file=cfg["users.config"],
+            file_exts=cfg["file_extensions"].split(),
+            anno_per_file=int(cfg["anno_per_file"])
+            )
+    
+    def handler(signum, frame):
+        server.exit()
+        
+    signal.signal(signal.SIGINT, handler)
+
+    server.run()
diff --git a/dfs/utils.py b/dfs/utils.py
new file mode 100644
index 0000000..e8a6d88
--- /dev/null
+++ b/dfs/utils.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+import hashlib
+
+from msg.stream import accept_msg, write_msg
+from msg.message import Message, OkMessage, KoMessage, NumMessage
+
+class UserInvalid(Exception):
+    pass
+
+def validate_user(conns, pass_file):
+    pass_dict = parse_passwd(pass_file)
+    login = accept_msg(conns, Message).get_contents()
+    passwd = accept_msg(conns, Message).get_contents()
+    if pass_dict.has_key(login) and pass_dict[login] == passwd:
+        write_msg(conns, OkMessage())
+        return login
+    else:
+        write_msg(conns, KoMessage())
+        raise UserInvalid(login)
+
+def parse_passwd(pass_file):
+    result = {}
+    with open(pass_file) as f:
+        for line in f:
+            login, passwd = line.split("=")
+            login = login.strip()
+            passwd = passwd.strip()
+            result[login] = passwd
+    return result
+
+def _check_sum(*args):
+    m = hashlib.sha256()
+    for arg in args:
+        m.update(arg)
+    return m
+
+def check_sum(*args):
+    return _check_sum(*args).digest()
+
+def check_hexsum(*args):
+    return _check_sum(*args).hexdigest()
+
+def send_contents(stream, contents):
+    write_msg(stream, NumMessage(len(contents)))
+    for ext, data in contents.iteritems():
+        write_msg(stream, Message(ext))
+        write_msg(stream, Message(data))
+        write_msg(stream, Message(check_sum(ext, data)))
+        accept_msg(stream, OkMessage)
+
+def receive_contents(stream):
+    contents = {}
+    n = accept_msg(stream, NumMessage).get_number()
+    for _ in range(n):
+        ext = accept_msg(stream, Message).get_contents()
+        data = accept_msg(stream, Message).get_contents()
+        csum = accept_msg(stream, Message).get_contents()
+        if csum == check_sum(ext, data):
+            write_msg(stream, OkMessage())
+        else:
+            write_msg(stream, KoMessage())
+        contents[ext] = data
+    return contents
diff --git a/example_configuration/passwd b/example_configuration/passwd
new file mode 100644
index 0000000..c8a9d0f
--- /dev/null
+++ b/example_configuration/passwd
@@ -0,0 +1,6 @@
+adj1 = adj1
+adj2 = adj2
+ann1 = ann1
+ann2 = ann2
+ann3 = ann3
+ann4 = ann4
diff --git a/example_configuration/server.cfg b/example_configuration/server.cfg
new file mode 100644
index 0000000..6c274a7
--- /dev/null
+++ b/example_configuration/server.cfg
@@ -0,0 +1,23 @@
+file_extensions = .mmax _words.xml _mentions.xml
+anno_per_file = 2
+
+[connection]
+host = localhost
+port = 2225
+backlog = 5	# maximum number of waiting connections
+{paths}		# paths relative to configuration file
+certfile = ssl.conf/cert.pem
+keyfile = ssl.conf/key.pem
+
+
+[svn]
+login = machine
+passwd = asdasdT7
+{paths}
+repository = ../data/repo_wc # path to working copy of repo
+
+
+[users]
+{paths}
+config = users.cfg
+passfile = passwd
diff --git a/example_configuration/ssl.conf/cert.pem b/example_configuration/ssl.conf/cert.pem
new file mode 100644
index 0000000..0dd2101
--- /dev/null
+++ b/example_configuration/ssl.conf/cert.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICkDCCAfmgAwIBAgIJAJPcESSEV86nMA0GCSqGSIb3DQEBBQUAMGExCzAJBgNV
+BAYTAlBMMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdD
+b21wYW55MSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlQGV4YW1wbGUuY29tMB4XDTEz
+MDEyMzEyMjIwN1oXDTE0MDEyMzEyMjIwN1owYTELMAkGA1UEBhMCUEwxDTALBgNV
+BAgMBFRlc3QxDTALBgNVBAcMBENpdHkxEDAOBgNVBAoMB0NvbXBhbnkxIjAgBgkq
+hkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
+gY0AMIGJAoGBAKUG9QHjUiCSjcvFQl1OqUgZ8c4Vzm7m7Ua7D33YDnGo3QGuhPML
+0JB+JPBG++MrPdQqMV8eeXe1dFlrm6SHnrEZIhwZg8iDnPhFKNjHiAkx0PCQdI93
+rAe4EcszkgbQCXXLGjQJNDikwGZ6ciylJ7C2XewWTlWjv7/LqMwhQotLAgMBAAGj
+UDBOMB0GA1UdDgQWBBRLRvOFIo9LIeiT2ohQalEzy687ijAfBgNVHSMEGDAWgBRL
+RvOFIo9LIeiT2ohQalEzy687ijAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA
+A4GBAD1odtgH1kZ4a0TEbwjK2PaHTYyofkkgeXsGGnG3eMkwKQ7PHJarBl7GtxP5
+Ls91k/KvBuNqCM/lPmM94USDSXrKGe0Zzc9WZbDG9SXO23gGOwPl+Tg9v1ocRHd2
+dYYIgZM8/tLcsHyNNMd7c4BOMD4rzkjgbThFFMl/2NSkvrO7
+-----END CERTIFICATE-----
diff --git a/example_configuration/ssl.conf/key.pem b/example_configuration/ssl.conf/key.pem
new file mode 100644
index 0000000..a179014
--- /dev/null
+++ b/example_configuration/ssl.conf/key.pem
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAKUG9QHjUiCSjcvF
+Ql1OqUgZ8c4Vzm7m7Ua7D33YDnGo3QGuhPML0JB+JPBG++MrPdQqMV8eeXe1dFlr
+m6SHnrEZIhwZg8iDnPhFKNjHiAkx0PCQdI93rAe4EcszkgbQCXXLGjQJNDikwGZ6
+ciylJ7C2XewWTlWjv7/LqMwhQotLAgMBAAECgYEAjx9XggmqgoFX5K/xfIbqHZQS
+uE8FN/2jL0Kwjs3AySZQdlLrDvbiLU6ZrqGBd2VbPBRTuVDuu7ltbNf8plbQcxaa
+yaKfKHPRYYIh/EqdaqsZ2bHHEARi03iNLDnktYz49I0ebFqgz0BGZVR3xDsjCu40
+s9Tku7IQsDdO/zVvreECQQDXHW1vli7iuOmTYfwKqeIV7wFPj0vp8XrUVOKqjiG1
+Fdf2ZlLrzeR/HbPbd2SL6u1dMV1j8wH/yPNRzFqNwOXpAkEAxGR5DzqrEE2ihyKL
+WzQn6tTTA9FI8WPbgXdTm/R3dev2OgMLZycHlSgptCJ1f+fMfGNhWhotg8kZfrO0
+9qnDEwJBAIpKrNQT5Mh0wBSz5WillmcrY9hV8yPOznw6pg2nmhvkkxYg6iYWE1N1
+MO9ibg1VVouW9McQrrRV57cqfPysiiECQHP63d9Xh1R+dKAXj3LSD0zIWMGlY81i
+amw/uvmb2ryiF+xUhfRqATld2ZsOypM5ofJHgmOCmCR+q3a+y/zrbk8CQQC0jx/T
+TYw9ULoUe0bdx9RfJkrCEr7/+/DLs4MFwPiQ/e8foHcUL+CM9EY/FkMfbWbKjtuQ
+iKXS/h5qp6NFdNVj
+-----END PRIVATE KEY-----
diff --git a/example_configuration/users.cfg b/example_configuration/users.cfg
new file mode 100644
index 0000000..aef6ccd
--- /dev/null
+++ b/example_configuration/users.cfg
@@ -0,0 +1,12 @@
+[auth]
+adjudicators = adj1 adj2
+
+[limits]
+# number of files downloaded for annotation
+annotation = 10
+# number of file pairs download for adjudication
+adjudication = 5
+
+# limits per user
+# adj1.annotation = 2
+# adj2.adjudication = 5
diff --git a/gui/CheckoutNumberDialog.py b/gui/CheckoutNumberDialog.py
new file mode 100644
index 0000000..83d6a03
--- /dev/null
+++ b/gui/CheckoutNumberDialog.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# generated by wxGlade 0.6.3 on Tue Sep 27 12:15:56 2011
+
+import wx
+
+# begin wxGlade: dependencies
+# end wxGlade
+
+# begin wxGlade: extracode
+
+# end wxGlade
+
+"""aktualnie nieużywane! """
+
+class CheckoutNumberDialog(wx.Dialog):
+    def __init__(self, *args, **kwds):
+        # begin wxGlade: CheckoutNumberDialog.__init__
+        kwds["style"] = wx.DEFAULT_DIALOG_STYLE
+        wx.Dialog.__init__(self, *args, **kwds)
+        self.label_4 = wx.StaticText(self, -1, "checkout files: ")
+        self.checkout_number = wx.SpinCtrl(self, -1, "1", min=1, max=300)
+        self.button_4 = wx.Button(self, wx.ID_YES, "OK")
+        self.button_5 = wx.Button(self, wx.ID_NO, "Cancel")
+
+        self.__set_properties()
+        self.__do_layout()
+
+        self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_YES)
+        self.Bind(wx.EVT_BUTTON, self.on_cancel, id=wx.ID_NO)
+        # end wxGlade
+
+    def __set_properties(self):
+        # begin wxGlade: CheckoutNumberDialog.__set_properties
+        self.SetTitle("checkout")
+        self.SetSize((193, 79))
+        # end wxGlade
+
+    def __do_layout(self):
+        # begin wxGlade: CheckoutNumberDialog.__do_layout
+        sizer_8 = wx.BoxSizer(wx.VERTICAL)
+        grid_sizer_3 = wx.GridSizer(1, 2, 0, 0)
+        grid_sizer_2 = wx.GridSizer(1, 2, 0, 0)
+        grid_sizer_2.Add(self.label_4, 0, wx.ALIGN_CENTER_VERTICAL, 0)
+        grid_sizer_2.Add(self.checkout_number, 0, wx.ALIGN_CENTER_VERTICAL, 0)
+        sizer_8.Add(grid_sizer_2, 1, 0, 0)
+        grid_sizer_3.Add(self.button_4, 0, 0, 0)
+        grid_sizer_3.Add(self.button_5, 0, 0, 0)
+        sizer_8.Add(grid_sizer_3, 0, wx.BOTTOM, 0)
+        self.SetSizer(sizer_8)
+        self.Layout()
+        self.Centre()
+        # end wxGlade
+
+    def on_ok(self, event): # wxGlade: CheckoutNumberDialog.<event_handler>
+        print "Event handler `on_ok' not implemented!"
+        event.Skip()
+
+    def on_cancel(self, event): # wxGlade: CheckoutNumberDialog.<event_handler>
+        print "Event handler `on_cancel' not implemented!"
+        event.Skip()
+
+# end of class CheckoutNumberDialog
+
+
diff --git a/gui/ConfigDialog.py b/gui/ConfigDialog.py
new file mode 100644
index 0000000..ff4fab6
--- /dev/null
+++ b/gui/ConfigDialog.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# generated by wxGlade 0.6.3 on Tue Sep 27 14:54:14 2011
+
+import wx
+
+import i18n
+_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors
+
+# begin wxGlade: dependencies
+# end wxGlade
+
+# begin wxGlade: extracode
+
+# end wxGlade
+
+class ConfigDialog(wx.Dialog):
+    def __init__(self, *args, **kwds):
+        # begin wxGlade: ConfigDialog.__init__
+        kwds["style"] = wx.DEFAULT_DIALOG_STYLE
+        wx.Dialog.__init__(self, *args, **kwds)
+        self.label_2 = wx.StaticText(self, -1, _("login")+":")
+        self.login_ctrl = wx.TextCtrl(self, -1, "")
+        self.label_6 = wx.StaticText(self, -1, _("password")+":")
+        self.passwd_ctrl = wx.TextCtrl(self, -1, "", style=wx.TE_PASSWORD)
+        self.label_5 = wx.StaticText(self, -1, _("extensions")+":")
+        self.exts_ctrl = wx.TextCtrl(self, -1, "", size=(250,30))
+        self.button_2 = wx.Button(self, wx.ID_OK, _("OK"))
+
+        self.__set_properties()
+        self.__do_layout()
+        # end wxGlade
+
+    def __set_properties(self):
+        # begin wxGlade: ConfigDialog.__set_properties
+        self.SetTitle(_("Directory configuration"))
+        self.label_2.SetToolTipString(_("Login supplied by system administrator"))
+        self.label_6.SetToolTipString(_("Password supplied by system administrator"))
+        self.label_5.SetToolTipString(_("List of space separated file extensions"))
+        self.button_2.SetMinSize((85, 29))
+        self.button_2.SetFocus()
+        # end wxGlade
+
+    def __do_layout(self):
+        # begin wxGlade: ConfigDialog.__do_layout
+        sizer_5 = wx.BoxSizer(wx.VERTICAL)
+        grid_sizer_4 = wx.GridSizer(3, 2, 0, 0)
+        grid_sizer_4.Add(self.label_2, 0, wx.ALIGN_CENTER_VERTICAL, 0)
+        grid_sizer_4.Add(self.login_ctrl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
+        grid_sizer_4.Add(self.label_6, 0, wx.ALIGN_CENTER_VERTICAL, 0)
+        grid_sizer_4.Add(self.passwd_ctrl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
+        grid_sizer_4.Add(self.label_5, 0, wx.ALIGN_CENTER_VERTICAL, 0)
+        grid_sizer_4.Add(self.exts_ctrl, 0, wx.ALIGN_CENTER_VERTICAL, 0)
+        sizer_5.Add(grid_sizer_4, 0, 0, 0)
+        sizer_5.Add(self.button_2, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
+        self.SetSizer(sizer_5)
+        sizer_5.Fit(self)
+        self.Layout()
+        # end wxGlade
+
+# end of class ConfigDialog
+
+
diff --git a/gui/DownloadNumberDialog.py b/gui/DownloadNumberDialog.py
new file mode 100644
index 0000000..d48e002
--- /dev/null
+++ b/gui/DownloadNumberDialog.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# generated by wxGlade 0.6.3 on Wed Sep 28 10:10:39 2011
+
+import wx
+
+import i18n
+_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors
+
+# begin wxGlade: dependencies
+# end wxGlade
+
+# begin wxGlade: extracode
+
+# end wxGlade
+
+class DownloadNumberDialog(wx.Dialog):
+    def __init__(self, *args, **kwds):
+        # begin wxGlade: DownloadNumberDialog.__init__
+        kwds["style"] = wx.DEFAULT_DIALOG_STYLE
+        wx.Dialog.__init__(self, *args, **kwds)
+        self.label_4 = wx.StaticText(self, -1, _("Download count")+":")
+        self.checkout_number = wx.SpinCtrl(self, -1, "1", min=1, max=300)
+        self.button_4 = wx.Button(self, wx.ID_YES, _("OK"))
+        self.button_5 = wx.Button(self, wx.ID_NO, _("Cancel"))
+
+        self.__set_properties()
+        self.__do_layout()
+
+        self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_YES)
+        self.Bind(wx.EVT_BUTTON, self.on_cancel, id=wx.ID_NO)
+        # end wxGlade
+
+    def __set_properties(self):
+        # begin wxGlade: DownloadNumberDialog.__set_properties
+        self.SetTitle(_("Download"))
+        self.label_4.SetToolTipString(_("Number of files to download"))
+        # end wxGlade
+
+    def __do_layout(self):
+        # begin wxGlade: DownloadNumberDialog.__do_layout
+        grid_sizer_2 = wx.GridSizer(2, 2, 20, 20)
+        grid_sizer_2.Add(self.label_4, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0)
+        grid_sizer_2.Add(self.checkout_number, 0, 0, 0)
+        grid_sizer_2.Add(self.button_4, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0)
+        grid_sizer_2.Add(self.button_5, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0)
+        self.SetSizer(grid_sizer_2)
+        grid_sizer_2.Fit(self)
+        self.Layout()
+        self.Centre()
+        # end wxGlade
+
+    def on_ok(self, event): # wxGlade: DownloadNumberDialog.<event_handler>
+        self.EndModal(wx.ID_OK)
+
+    def on_cancel(self, event): # wxGlade: DownloadNumberDialog.<event_handler>
+        self.EndModal(wx.ID_CANCEL)
+
+# end of class DownloadNumberDialog
+
+
diff --git a/gui/LogDialog.py b/gui/LogDialog.py
new file mode 100644
index 0000000..7863404
--- /dev/null
+++ b/gui/LogDialog.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# generated by wxGlade 0.6.3 on Tue Sep 27 12:15:56 2011
+
+import wx
+
+# begin wxGlade: dependencies
+# end wxGlade
+
+# begin wxGlade: extracode
+
+# end wxGlade
+
+"""aktualnie nieużywane! """
+
+class LogDialog(wx.Dialog):
+    def __init__(self, *args, **kwds):
+        # begin wxGlade: LogDialog.__init__
+        kwds["style"] = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.THICK_FRAME
+        wx.Dialog.__init__(self, *args, **kwds)
+
+        self.__set_properties()
+        self.__do_layout()
+        # end wxGlade
+
+    def __set_properties(self):
+        # begin wxGlade: LogDialog.__set_properties
+        self.SetTitle("Log")
+        self.SetSize((400, 315))
+        # end wxGlade
+
+    def __do_layout(self):
+        # begin wxGlade: LogDialog.__do_layout
+        self.Layout()
+        # end wxGlade
+
+    def on_ok(self, event): # wxGlade: LogDialog.<event_handler>
+        print "Event handler `on_ok' not implemented!"
+        event.Skip()
+
+# end of class LogDialog
+
+
diff --git a/gui/LoginDialog.py b/gui/LoginDialog.py
new file mode 100644
index 0000000..9383d7e
--- /dev/null
+++ b/gui/LoginDialog.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# generated by wxGlade 0.6.3 on Tue Sep 27 12:15:56 2011
+
+import wx
+
+# begin wxGlade: dependencies
+# end wxGlade
+
+# begin wxGlade: extracode
+
+# end wxGlade
+
+"""aktualnie nieużywane! """
+
+class LoginDialog(wx.Dialog):
+    def __init__(self, *args, **kwds):
+        # begin wxGlade: LoginDialog.__init__
+        kwds["style"] = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.THICK_FRAME
+        wx.Dialog.__init__(self, *args, **kwds)
+        self.label_1 = wx.StaticText(self, -1, "login: ")
+        self.login_ctrl = wx.TextCtrl(self, -1, "")
+        self.label_3 = wx.StaticText(self, -1, "password: ")
+        self.passwd_ctrl = wx.TextCtrl(self, -1, "", style=wx.TE_PASSWORD)
+        self.save_passwd = wx.CheckBox(self, -1, "save password")
+        self.button_1 = wx.Button(self, wx.ID_OK, "OK")
+
+        self.__set_properties()
+        self.__do_layout()
+
+        self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK)
+        # end wxGlade
+
+    def __set_properties(self):
+        # begin wxGlade: LoginDialog.__set_properties
+        self.SetTitle("login")
+        self.button_1.SetMinSize((85, 32))
+        # end wxGlade
+
+    def __do_layout(self):
+        # begin wxGlade: LoginDialog.__do_layout
+        sizer_4 = wx.BoxSizer(wx.VERTICAL)
+        grid_sizer_1 = wx.GridSizer(2, 2, 0, 0)
+        grid_sizer_1.Add(self.label_1, 0, wx.ALIGN_CENTER_VERTICAL, 0)
+        grid_sizer_1.Add(self.login_ctrl, 0, 0, 0)
+        grid_sizer_1.Add(self.label_3, 0, wx.ALIGN_CENTER_VERTICAL, 0)
+        grid_sizer_1.Add(self.passwd_ctrl, 0, 0, 0)
+        sizer_4.Add(grid_sizer_1, 0, 0, 0)
+        sizer_4.Add(self.save_passwd, 0, 0, 0)
+        sizer_4.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
+        self.SetSizer(sizer_4)
+        sizer_4.Fit(self)
+        self.Layout()
+        # end wxGlade
+
+    def on_ok(self, event): # wxGlade: LoginDialog.<event_handler>
+        print "Event handler `on_ok' not implemented!"
+        event.Skip()
+
+# end of class LoginDialog
+
+
diff --git a/gui/MainFrame.py b/gui/MainFrame.py
new file mode 100644
index 0000000..402945d
--- /dev/null
+++ b/gui/MainFrame.py
@@ -0,0 +1,227 @@
+# -*- coding: utf-8 -*-
+# generated by wxGlade 0.6.3 on Tue Sep 27 12:15:56 2011
+
+import wx
+
+import i18n
+_ = i18n.language.ugettext
+
+from DownloadNumberDialog import DownloadNumberDialog
+from ReturnReasonDialog import ReturnReasonDialog
+from SortableListCtrl import SortableListCtrl
+
+import const
+import utils
+
+# begin wxGlade: dependencies
+# end wxGlade
+
+# begin wxGlade: extracode
+
+# end wxGlade
+
+class MainFrame(wx.Frame):
+    def __init__(self, *args, **kwds):
+        # begin wxGlade: MainFrame.__init__
+        kwds["style"] = wx.DEFAULT_FRAME_STYLE
+        wx.Frame.__init__(self, *args, **kwds)
+        self.window_1 = wx.SplitterWindow(self, -1, style=wx.SP_3D|wx.SP_BORDER)
+        self.window_1_pane_2 = wx.Panel(self.window_1, -1)
+        self.window_1_pane_1 = wx.Panel(self.window_1, -1)
+        self.notebook_1 = wx.Notebook(self.window_1_pane_1, -1, style=wx.BK_DEFAULT)        
+        self.notebook_1_pane_1 = wx.Panel(self.notebook_1, -1)
+        self.notebook_1_pane_2 = wx.Panel(self.notebook_1, -1)        
+
+        self.log_ctrl = wx.TextCtrl(self.window_1_pane_2, -1, "", style=wx.TE_MULTILINE|wx.TE_READONLY)
+
+        self.files_list = SortableListCtrl(self.notebook_1_pane_1, self.log_ctrl, False, const.program, const.extension)
+        self.super_files_list = SortableListCtrl(self.notebook_1_pane_2, self.log_ctrl, True, const.program, const.extension)
+        
+        # Menu Bar
+        self.frame_1_menubar = wx.MenuBar()
+        self.dir_menu = wx.Menu()
+        self.dir_menu_open = wx.MenuItem(self.dir_menu, 5, _("Open"), _("Open new directory"), wx.ITEM_NORMAL)
+        self.dir_menu.AppendItem(self.dir_menu_open)
+        self.dir_menu.Append(6, _("Reconfigure"), _("Reconfigure directory"), wx.ITEM_NORMAL)
+        self.dir_menu.AppendSeparator()
+        if not const.hide_dir_menu == "yes":
+            self.frame_1_menubar.Append(self.dir_menu, _("Directory"))
+        wxglade_tmp_menu = wx.Menu()
+        wxglade_tmp_menu.Append(1, _("Upload Selected"), _("Upload annotated or adjudicated files"), wx.ITEM_NORMAL)
+        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)
+        wxglade_tmp_menu.Append(10, _("Return Selected"), _("Return annotated or adjudicated because they are not suitable for annotation or contain errors"), wx.ITEM_NORMAL)
+        wxglade_tmp_menu.AppendSeparator()
+        wxglade_tmp_menu.Append(2, _("Download (annotation)"), _("Download files for annotation"), wx.ITEM_NORMAL)
+        wxglade_tmp_menu.Append(3, _("Download (adjudication)"), _("Download files for adjudication"), wx.ITEM_NORMAL)
+        wxglade_tmp_menu.AppendSeparator()
+        wxglade_tmp_menu.Append(7, _("Checkout"), _("Checkout files from server to your local, empty directory"), wx.ITEM_NORMAL)
+        
+        self.frame_1_menubar.Append(wxglade_tmp_menu, _("Files"))
+        wxglade_tmp_menu = wx.Menu()
+        wxglade_tmp_menu.Append(4, _("Ready"), _("Select items which are marked for upload"), wx.ITEM_NORMAL)
+        wxglade_tmp_menu.Append(9, _("Modified"), _("Select items which are modified"), wx.ITEM_NORMAL)
+        self.frame_1_menubar.Append(wxglade_tmp_menu, _("Select"))
+                
+        wxglade_tmp_menu = wx.Menu()
+        wxglade_tmp_menu.Append(11, _("Finished count"), _("Check number of finished texts"), wx.ITEM_NORMAL)
+        self.frame_1_menubar.Append(wxglade_tmp_menu, _("Annotation"))
+        
+        self.SetMenuBar(self.frame_1_menubar)
+        # Menu Bar end
+        
+        self.CreateStatusBar()                        
+
+        self.__set_properties()
+        self.__do_layout()
+
+        self.Bind(wx.EVT_MENU, self.open_directory, self.dir_menu_open)
+        self.Bind(wx.EVT_MENU, self.reconf_directory, id=6)
+        self.Bind(wx.EVT_MENU, self.upload, id=1)
+        self.Bind(wx.EVT_MENU, self.checkin, id=8)
+        self.Bind(wx.EVT_MENU, self.download, id=2)
+        self.Bind(wx.EVT_MENU, self.download_prim, id=3)
+        self.Bind(wx.EVT_MENU, self.checkout, id=7)
+        self.Bind(wx.EVT_MENU, self.select_ready, id=4)
+        self.Bind(wx.EVT_MENU, self.select_modified, id=9)
+        self.Bind(wx.EVT_MENU, self.return_files, id=10)
+        self.Bind(wx.EVT_MENU, self.check_stats, id=11)
+        
+        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.activate_item, self.files_list)        
+        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.activate_super_item, self.super_files_list)
+
+    def __set_properties(self):
+        # begin wxGlade: MainFrame.__set_properties
+        self.SetTitle("--")
+        self.SetSize((663, 556))
+        self.SetFocus()
+        # end wxGlade
+
+    def __do_layout(self):
+        # begin wxGlade: MainFrame.__do_layout
+        sizer_6 = wx.BoxSizer(wx.HORIZONTAL)
+        sizer_7 = wx.BoxSizer(wx.VERTICAL)
+        sizer_1 = wx.BoxSizer(wx.VERTICAL)
+        sizer_3 = wx.BoxSizer(wx.VERTICAL)
+        sizer_2 = wx.BoxSizer(wx.VERTICAL)
+        sizer_2.Add(self.files_list, 1, wx.EXPAND, 0)
+        self.notebook_1_pane_1.SetSizer(sizer_2)
+        sizer_3.Add(self.super_files_list, 1, wx.EXPAND, 0)
+        self.notebook_1_pane_2.SetSizer(sizer_3)
+        self.notebook_1.AddPage(self.notebook_1_pane_1, _("Annotation"))
+        self.notebook_1.AddPage(self.notebook_1_pane_2, _("Adjudication"))
+        sizer_1.Add(self.notebook_1, 1, wx.EXPAND, 0)
+        self.window_1_pane_1.SetSizer(sizer_1)
+        sizer_7.Add(self.log_ctrl, 1, wx.EXPAND, 0)
+        self.window_1_pane_2.SetSizer(sizer_7)
+        self.window_1.SplitHorizontally(self.window_1_pane_1, self.window_1_pane_2, -100)
+        sizer_6.Add(self.window_1, 1, wx.EXPAND, 0)
+        self.SetSizer(sizer_6)
+        self.Layout()
+        self.Centre()
+        # end wxGlade
+
+    def set_manager(self, manager):
+        self.manager = manager
+
+    def open_directory(self, event): # wxGlade: MainFrame.<event_handler>
+        if event.GetId() == self.dir_menu_open.GetId():
+            dlg = wx.DirDialog(self, message=_("Choose a directory"))
+            try:
+                if dlg.ShowModal() == wx.ID_OK:
+                    self.manager.set_directory(dlg.GetPath())
+            finally:
+                dlg.Destroy()
+        else:
+            item = self.dir_menu.FindItemById(event.GetId())
+            self.manager.set_directory(item.GetLabel())
+
+    def upload(self, event): # wxGlade: MainFrame.<event_handler>
+        names = ( self.files_list.get_selected()
+                + self.super_files_list.get_selected() )
+        info = ( _("Do you want to upload the following files?")+"\n\n"
+               + "\n".join(names) )
+        dial = wx.MessageDialog( self, info, _('Question')
+                               , wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION )
+        try:
+            if dial.ShowModal() == wx.ID_YES:
+                self.manager.upload(names, checkin=False)
+        finally:
+            dial.Destroy()
+            
+    def return_files(self, event): # wxGlade: MainFrame.<event_handler>
+        names = ( self.files_list.get_selected()
+                + self.super_files_list.get_selected() )
+        reason = None
+        dialog = ReturnReasonDialog(self, names)
+        try:
+            if dialog.ShowModal() == wx.ID_OK:
+                reason = dialog.reason.GetValue().encode("utf-8")
+        finally:
+            dialog.Destroy()
+        if reason is not None:
+            self.manager.return_files(names, reason)       
+
+    def download(self, event): # wxGlade: MainFrame.<event_handler>
+        num = None
+        dialog = DownloadNumberDialog(self, -1)
+        try:
+            if dialog.ShowModal() == wx.ID_OK:
+                num = dialog.checkout_number.GetValue()
+        finally:
+            dialog.Destroy()
+        if num is not None:
+            self.manager.download(num)
+
+    def download_prim(self, event): # wxGlade: MainFrame.<event_handler>
+        num = None
+        dialog = DownloadNumberDialog(self, -1)
+        try:
+            if dialog.ShowModal() == wx.ID_OK:
+                num = dialog.checkout_number.GetValue()
+        finally:
+            dialog.Destroy()
+        if num is not None:
+            self.manager.download_prim(num)
+
+    def activate_item(self, event): # wxGlade: MainFrame.<event_handler>
+        utils.change_ready( self.manager.ready
+                          , self.files_list
+                          , event.GetIndex() )
+
+    def activate_super_item(self, event): # wxGlade: MainFrame.<event_handler>
+        utils.change_ready_prim( self.manager.ready
+                               , self.super_files_list
+                               , event.GetIndex() )
+
+    def reconf_directory(self, event): # wxGlade: MainFrame.<event_handler>
+        self.manager.reconf_directory()
+
+    def select_ready(self, event): # wxGlade: MainFrame.<event_handler>
+        self.files_list.select_ready()
+        self.super_files_list.select_ready()
+
+    def checkout(self, event): # wxGlade: MainFrame.<event_handler>
+        self.manager.checkout()
+
+    def checkin(self, event): # wxGlade: MainFrame.<event_handler>
+        names = ( self.files_list.get_selected()
+                + self.super_files_list.get_selected() )
+        info = ( _("Do you want to checkin the following files?")+"\n\n"
+               + "\n".join(names) 
+               + "\n\n"+_("Files will be deleted from the local directory and you will not be able to read, write or modify them later."))
+        dial = wx.MessageDialog( self, info, _('Question')
+                               , wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION )
+        try:
+            if dial.ShowModal() == wx.ID_YES:
+                self.manager.upload(names, checkin=True)
+        finally:
+            dial.Destroy()
+
+    def select_modified(self, event): # wxGlade: MainFrame.<event_handler>
+        self.files_list.select_modified()
+        self.super_files_list.select_modified()
+            
+    def check_stats(self, event): # wxGlade: MainFrame.<event_handler>        
+        self.manager.check_stats()            
+        
+# end of class MainFrame
diff --git a/gui/ReturnReasonDialog.py b/gui/ReturnReasonDialog.py
new file mode 100644
index 0000000..2e579aa
--- /dev/null
+++ b/gui/ReturnReasonDialog.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+# generated by wxGlade 0.6.3 on Wed Sep 28 10:10:39 2011
+
+import wx
+
+import i18n
+_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors
+
+# begin wxGlade: dependencies
+# end wxGlade
+
+# begin wxGlade: extracode
+
+# end wxGlade
+
+class ReturnReasonDialog(wx.Dialog):
+    def __init__(self, parent, names):
+        # begin wxGlade: ReturnReasonDialog.__init__
+        wx.Dialog.__init__(self, parent, -1)
+        
+        info = ( _("Why do you want to return the following files?")+"\n"
+               + "\n".join(names) )
+        
+        self.label_1 = wx.StaticText(self, -1, info)         
+        self.label_4 = wx.StaticText(self, -1, _("Reason")+":")
+        self.reason = wx.TextCtrl(self, -1, "", size=(250, 80), style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER)
+        self.button_4 = wx.Button(self, wx.ID_YES, _("OK"))
+        self.button_5 = wx.Button(self, wx.ID_NO, _("Cancel"))
+
+        self.__set_properties()
+        self.__do_layout()
+
+        self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_YES)
+        self.Bind(wx.EVT_BUTTON, self.on_cancel, id=wx.ID_NO)
+        # end wxGlade
+
+    def __set_properties(self):
+        # begin wxGlade: ReturnReasonDialog.__set_properties
+        self.SetTitle(_("Return files"))        
+        # end wxGlade
+
+    def __do_layout(self):
+        # begin wxGlade: ReturnReasonDialog.__do_layout                
+        sizer_2 = wx.BoxSizer(wx.HORIZONTAL)        
+        sizer_2.Add(self.label_4, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0)
+        sizer_2.Add(self.reason, 0, 0, 0)
+        
+        sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
+        sizer_3.Add(self.button_4, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0)
+        sizer_3.Add(self.button_5, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0)
+        
+        sizer_1 = wx.BoxSizer(wx.VERTICAL)    
+        sizer_1.Add(self.label_1, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 1)
+        sizer_1.Add(sizer_2, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 1)
+        sizer_1.Add(sizer_3, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 1)
+        self.SetSizer(sizer_1)
+        sizer_1.Fit(self)
+        self.Layout()
+        # end wxGlade
+
+    def on_ok(self, event): # wxGlade: ReturnReasonDialog.<event_handler>
+        self.EndModal(wx.ID_OK)
+
+    def on_cancel(self, event): # wxGlade: ReturnReasonDialog.<event_handler>
+        self.EndModal(wx.ID_CANCEL)
+
+# end of class ReturnReasonDialog
+
+
diff --git a/gui/SortableListCtrl.py b/gui/SortableListCtrl.py
new file mode 100644
index 0000000..71e7516
--- /dev/null
+++ b/gui/SortableListCtrl.py
@@ -0,0 +1,160 @@
+# -*- coding: utf-8 -*-
+import  threading
+from PIL import Image
+
+import i18n
+_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors
+
+import  os.path
+import  wx
+import  wx.lib.mixins.listctrl  as  listmix
+import subprocess
+
+try:
+    from agw import ultimatelistctrl as ULC
+except ImportError: # if it's not there locally, try the wxPython lib.
+    from wx.lib.agw import ultimatelistctrl as ULC
+    
+class SortableListCtrl(ULC.UltimateListCtrl, listmix.ColumnSorterMixin, listmix.ListCtrlAutoWidthMixin):
+    def __init__(self, parent, log, superann, program, extension):
+        
+        self._lock = threading.Lock()
+        
+        self.program = program
+        self.extension = extension
+                
+        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 )
+        listmix.ListCtrlAutoWidthMixin.__init__(self)
+        
+        self.log_ctrl = log
+        
+        self.il = wx.ImageList(16, 16)      
+        up = wx.Image(os.path.join('img', 'up.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap()
+        down = wx.Image(os.path.join('img', 'down.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap()
+        if superann:
+            textimg_1 = wx.Image(os.path.join('img', 'textimg_2.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap()
+        else:
+            textimg_1 = wx.Image(os.path.join('img', 'textimg_1.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap()
+        ready = wx.Image(os.path.join('img', 'ready.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap()
+        self.sm_up = self.il.Add(up)
+        self.sm_dn = self.il.Add(down)
+        self.textimg_1 = self.il.Add(textimg_1)
+        self.ready = self.il.Add(ready)
+                
+        self.AssignImageList(self.il, wx.IMAGE_LIST_SMALL)
+        
+        self.SetUserLineHeight(23)
+        self.InsertColumn(0, _("File"))
+        self.InsertColumn(1, _("Status"))
+        self.InsertColumn(2, _("Ready"))
+        self.InsertColumn(3, _("Stage"))
+        self.InsertColumn(4, "")
+        self.SetColumnWidth(0, 80)
+        self.SetColumnWidth(1, 100)
+        self.SetColumnWidth(2, 120)
+        self.SetColumnWidth(3, 100)
+                
+        self.itemDataMap = {}
+        
+        listmix.ColumnSorterMixin.__init__(self, 5)
+                
+    # required by ColumnSorterMixin
+    def GetListCtrl(self):
+        return self
+    
+    def GetSortImages(self):
+        return (self.sm_dn, self.sm_up)
+    # required by ColumnSorterMixin - end
+    
+    def show(self, data):    
+        self._lock.acquire()        
+        try:
+            # for some reason deleteAllItems() doesn't work with buttons inside ulc
+            for item in range(self.GetItemCount()):
+                self.DeleteItem(0)            
+            self.itemDataMap.clear()
+            for i, (file_name, status, is_ready, stage) in enumerate(data):
+                img = self.textimg_1
+                if is_ready == _("Yes"):
+                    img = self.ready                    
+                pos = self.InsertImageStringItem(i, file_name, img)
+                self.SetStringItem(pos, 1, status)
+                self.SetStringItem(pos, 2, is_ready)
+                self.SetStringItem(pos, 3, stage)
+                
+                button = wx.Button(self, id=i, size=(80, 23), label=_("Open"))                        
+                self.SetItemWindow(pos, col=4, wnd=button, expand=False)                
+                button.Bind(wx.EVT_BUTTON, self.open_button)
+                
+                self.SetItemData(pos, i)
+                
+                self.itemDataMap[i] = (file_name, status, is_ready, stage, None)
+        finally:
+            self._lock.release()
+            
+    def shown_data(self):
+        result = []
+        for i in range(self.GetItemCount()):
+            part_result = []
+            for j in range(0, 4):
+                part_result.append(self.GetItem(i, j).GetText())
+            result.append(tuple(part_result))
+        return result
+    
+    def SetReadyYes(self, pos):
+        self.SetItemImage(pos, self.ready)
+        return self._set_ready(pos, _("Yes"))
+    
+    def SetReadyNo(self, pos):
+        self.SetItemImage(pos, self.textimg_1)
+        return self._set_ready(pos, _("No"))
+
+    def _set_ready(self, pos, val):
+        file_name = self.GetItem(pos, 0).GetText()        
+        for key, (filename, status, ready, stage, sth) in self.itemDataMap.iteritems():
+            if filename == file_name:
+                self.itemDataMap[key] = (filename, status, val, stage, sth)
+                break
+        return self.SetStringItem(pos, 2, val)
+    
+    def GetFilename(self, pos):
+        return self.GetItem(pos, 0).GetText()
+    
+    def IsReady(self, pos):
+        return self.GetItem(pos, 2).GetText() == _("Yes")
+    
+    def select_ready(self):
+        return self._select(lambda list, pos : list.GetItem(pos, 2).GetText() == _("Yes"))
+
+    def select_modified(self):
+        return self._select(lambda list, pos : list.GetItem(pos, 1).GetText() == _("Modified"))
+        
+    def _select(self, cond):
+        for i in range(self.GetItemCount()):
+            if cond(self, i):
+                self.Select(i, 1)
+            else:
+                self.Select(i, 0)
+                
+    def get_selected(self):
+        fnames = []
+        for i in range(self.GetItemCount()):
+            if self.IsSelected(i):
+                fnames.append(self.GetFilename(i))
+        return fnames
+        
+    def open_button(self, event):
+        file_name = self.GetFilename(event.GetEventObject().GetId())
+        print event.GetEventObject().GetId()
+        if file_name:            
+            self.log_ctrl.AppendText(_("Starting ") + self.program + _(" for text") + " " + file_name + "...\n")                        
+            app = os.path.join("..", self.program, self.program+".exe")
+            app_bin = os.path.join("..", self.program, self.program)
+            if not os.path.exists(app):
+                app = app_bin
+            text = os.path.join("..", "teksty", file_name + self.extension)
+            try:
+                subprocess.Popen([app, text])
+            except Exception as ex:
+                self.log_ctrl.AppendText(_("Error starting ") + self.program + ": " + unicode(str(ex), errors='replace') + "\n")            
+    
diff --git a/gui/client.py b/gui/client.py
new file mode 100644
index 0000000..db13f25
--- /dev/null
+++ b/gui/client.py
@@ -0,0 +1,470 @@
+# -*- coding: utf-8 -*-
+import sys
+import os
+import shutil
+import tempfile
+import socket
+import ssl
+import pprint
+from optparse import OptionParser
+import traceback
+import re
+
+import i18n
+_ = i18n.language.ugettext
+
+from dfs.msg.stream import write_msg, read_msg, accept_msg
+from dfs.msg.message import *
+from dfs.utils import check_sum, send_contents, receive_contents
+
+from utils import normal_files, super_files
+
+def init_stream(host, port, cert, log=None):    
+    log_msg(_("Connecting server..."), log)
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    stream = ssl.wrap_socket(s, ca_certs=cert, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1)
+    stream.connect((host, port))
+    return stream
+
+def upload(stream, files, src_dir, file_exts, checkin=False, log=None):
+    """Checkin / Upload files to the server."""
+
+    def is_adj(path):
+        return False if len(os.path.dirname(path)) == 0 else True
+    
+    log_msg(_("Sending request..."), log)
+    if checkin:
+        write_msg(stream, CheckinRequest(len(files)))
+    else:
+        write_msg(stream, UploadRequest(len(files)))
+    done = []
+    
+    failed = set()
+    for file_path in files:
+        log_msg(_("Uploading") + " " + file_path + "...", log)
+
+        if not is_adj(file_path):
+            file_id = file_path
+            contents = read_contents(src_dir, file_path, file_exts)
+        else:
+            file_id, choice = os.path.split(file_path)
+            contents = read_contents(os.path.join(src_dir, file_id), choice, file_exts)
+
+        write_msg(stream, Message(file_id))
+        write_msg(stream, BoolMessage(is_adj(file_path)))
+        msg = read_msg(stream)
+        if type(msg) != OkMessage:
+            log_msg(_("Upload failed: server doesn't accept this file from you"), log)
+            failed.add(file_path)
+            continue
+        send_contents(stream, contents)
+
+    write_msg(stream, ClientDone())
+    accept_msg(stream, ServerDone)
+    
+    if not checkin:
+        log_msg(_("Done"), log)
+        return
+    
+    # When checkin command
+    for file_path in files:
+        if file_path in failed:
+            continue
+        if not is_adj(file_path):
+            log_msg(_("Deleting") + " " + file_path + "...", log)
+            for ext in file_exts:
+                os.remove(os.path.join(src_dir, file_path + ext))
+        else:
+            file_id, ver = os.path.split(file_path)
+            log_msg(_("Deleting") + " " + file_id + "...", log)
+            shutil.rmtree(os.path.join(src_dir, file_id))
+
+    log_msg(_("Done"), log)
+
+def download(stream, n, dest_dir, tmp_dir, log=None):
+    """Download from server n files."""
+
+    log_msg(_("Sending request..."), log)
+    write_msg(stream, DownloadRequest(n))
+    n = accept_msg(stream, NumMessage).get_number()
+    if n == 0:
+        log_msg(_("No files to download"), log)        
+
+    done = []
+    for i in range(n):
+        file_id = accept_msg(stream, Message).get_contents()
+        log_msg(_("Downloading") + " " + file_id + "...", log)
+        contents = receive_contents(stream)
+        save_contents(contents, tmp_dir, file_id)
+        for ext in contents.keys():
+            done.append(file_id + ext)
+
+    write_msg(stream, ClientDone())
+    accept_msg(stream, ServerDone)
+    for file_name in done:
+        from_path = os.path.join(tmp_dir, file_name)
+        to_path = os.path.join(dest_dir, file_name)
+        shutil.move(from_path, to_path)
+    log_msg(_("Done"), log)
+
+def download_prim(stream, n, dest_dir, tmp_dir, log=None):
+    """Download from server n pairs of files for adjudication."""
+
+    log_msg(_("Sending request..."), log)
+    write_msg(stream, DownloadPrimRequest(n))
+
+    msg = read_msg(stream)
+    if type(msg) != OkMessage:
+        log_msg(_("FAILED: you don't have adjudicator privileges"), log)
+        return
+
+    n = accept_msg(stream, NumMessage).get_number()
+    anno_per_file = accept_msg(stream, NumMessage).get_number()
+    if n == 0:
+        log_msg(_("No files to download"), log)                
+
+    done = []
+    for i in range(n):
+        file_id = accept_msg(stream, Message).get_contents()
+        log_msg(_("Downloading") + " " + file_id + "...", log)            
+        
+        file_dir = os.path.join(tmp_dir, file_id) 
+        os.mkdir(file_dir)
+        done.append(file_id)
+                
+        for j in range(anno_per_file):
+            save_contents(receive_contents(stream), file_dir, chr(ord('A') + j))
+            
+        if accept_msg(stream, BoolMessage).get_boolean() == True:
+            if anno_per_file == 1:
+                save_contents(receive_contents(stream), file_dir, "A")
+            else:           
+                save_contents(receive_contents(stream), file_dir, "Super")
+                
+    write_msg(stream, ClientDone())
+    accept_msg(stream, ServerDone)
+    for fild_id in done:
+        from_path = os.path.join(tmp_dir, fild_id)
+        to_path = os.path.join(dest_dir, fild_id)
+        shutil.move(from_path, to_path)
+    log_msg(_("Done"), log)
+
+def check_stats(stream, log=None):
+    """Check annotatator stats."""
+
+    log_msg(_("Sending request..."), log)
+    write_msg(stream, StatsRequest())
+    n = accept_msg(stream, NumMessage).get_number()
+    log_msg(_("Number of finished files: ") + str(n), log)
+    write_msg(stream, ClientDone())
+    accept_msg(stream, ServerDone)    
+    log_msg(_("Done"), log)
+    
+def checkout(stream, dest_dir, tmp_dir, exts, log=None):
+    log_msg(_("Sending request..."), log)
+    write_msg(stream, CheckoutRequest())
+
+    done = []
+    n = accept_msg(stream, NumMessage).get_number()
+    for i in range(n):
+        file_id = accept_msg(stream, Message).get_contents()
+        log_msg(_("Downloading") + " " + file_id + "...", log)
+
+        contents = receive_contents(stream)
+        save_contents(contents, tmp_dir, file_id)
+        for ext in contents.keys():
+            done.append(file_id + ext)
+
+    n = accept_msg(stream, NumMessage).get_number()
+    anno_per_file = accept_msg(stream, NumMessage).get_number()
+    
+    for i in range(n):
+        file_id = accept_msg(stream, Message).get_contents()
+        log_msg(_("Downloading") + " " + file_id + "...", log)
+        
+        file_dir = os.path.join(tmp_dir, file_id)
+        os.mkdir(file_dir)        
+        
+        for j in range(anno_per_file):
+            save_contents(receive_contents(stream), file_dir, chr(j + ord('A')))
+        
+        if accept_msg(stream, BoolMessage).get_boolean() == True:
+            if anno_per_file == 1:
+                save_contents(receive_contents(stream), file_dir, "A")
+            else:
+                save_contents(receive_contents(stream), file_dir, "Super")                
+
+        done.append(file_id)
+
+    write_msg(stream, ClientDone())
+    accept_msg(stream, ServerDone)
+        
+    log_msg(_("Deleting local files..."), log)
+    try:
+        # normal texts
+        for f in normal_files(dest_dir, exts):        
+            for ext in exts:
+                os.remove(os.path.join(dest_dir, f + ext))
+        
+        # super annotated texts        
+        dirs = set()
+        for f in super_files(dest_dir, exts):
+            dir = f.split(os.path.sep)[0]
+            dirs.add(dir)        
+        for dir in dirs:  
+            shutil.rmtree(os.path.join(dest_dir, dir))
+                                
+    except Exception as ex:
+        log_msg(_("Error occured:") + unicode(str(ex), errors='replace'), log)
+            
+    log_msg(_("Saving dowloaded files..."), log)    
+    for path in done:
+        from_path = os.path.join(tmp_dir, path)
+        to_path = os.path.join(dest_dir, path)
+        shutil.move(from_path, to_path)
+        
+    log_msg(_("Done"), log)
+
+def return_files(stream, files, reason, src_dir, file_exts, log=None):
+    """Return files to the server."""
+
+    def is_adj(path):
+        return False if len(os.path.dirname(path)) == 0 else True
+    
+    log_msg(_("Sending request..."), log)    
+    write_msg(stream, ReturnRequest(len(files)))
+    write_msg(stream, Message(reason))
+    
+    done = []
+        
+    failed = set()
+    for file_path in files:
+        log_msg(_("Uploading") + " " + file_path + "...", log)
+
+        if not is_adj(file_path):
+            file_id = file_path
+            contents = read_contents(src_dir, file_path, file_exts)
+        else:
+            file_id, choice = os.path.split(file_path)
+            contents = read_contents(os.path.join(src_dir, file_id), choice, file_exts)
+
+        write_msg(stream, Message(file_id))
+        write_msg(stream, BoolMessage(is_adj(file_path)))
+        msg = read_msg(stream)
+        if type(msg) != OkMessage:
+            log_msg(_("Upload failed: server doesn't accept this file from you"), log)
+            failed.add(file_path)
+            continue
+        send_contents(stream, contents)
+
+    write_msg(stream, ClientDone())
+    accept_msg(stream, ServerDone)
+    
+    for file_path in files:
+        if file_path in failed:
+            continue
+        if not is_adj(file_path):
+            log_msg(_("Deleting") + " " + file_path + "...", log)
+            for ext in file_exts:
+                os.remove(os.path.join(src_dir, file_path + ext))
+        else:
+            file_id, ver = os.path.split(file_path)
+            log_msg(_("Deleting") + " " + file_id + "...", log)
+            shutil.rmtree(os.path.join(src_dir, file_id))
+
+    log_msg(_("Done"), log)
+    
+def save_contents(contents, dest_dir, file_id):
+    for ext, data in contents.iteritems():
+        dest_path = os.path.join(dest_dir, file_id + ext)
+        with open(dest_path, "w") as dest:
+            dest.write(data)
+
+def read_contents(src_dir, file_id, file_exts):
+    result = {}
+    regex = re.compile("%s(.*)$" % file_id)
+    for entry in os.listdir(src_dir):
+        match = regex.match(entry)
+        if not match:
+            continue
+        with open(os.path.join(src_dir, entry)) as src:
+            ext = match.group(1)
+            if ext in file_exts:
+                result[ext] = src.read()
+            else:
+                print "Skipping file " + entry
+    return result
+
+def run_auth(stream, login, passwd, log=None):
+    log_msg(_("Authentication..."), log)
+    write_msg(stream, Message(login))
+    write_msg(stream, Message(passwd))
+    msg = read_msg(stream)
+    if type(msg) != OkMessage:
+        log_msg(_("FAILED: incorrect login or password"), log)
+        return False
+    return True
+
+def run_upload(login, passwd, host, port, cert, files, src_dir, file_exts, checkin=False, log=None):
+    result = True
+    try:
+        stream = init_stream(host, port, cert, log=log)
+        try:
+            if run_auth(stream, login, passwd, log=log):                
+                upload(stream, files, src_dir, file_exts, checkin=checkin, log=log)
+        finally:
+            stream.close()
+    except:
+        log_msg(_("FAILED: unable to connect to server"), log)
+        print traceback.format_exc()
+        result = False
+    if log:
+        log.put(None)
+    return result
+
+def run_download(login, passwd, host, port, cert, n, dest_dir, log=None):
+    result = True
+    try:
+        stream = init_stream(host, port, cert, log=log)
+        try:
+            tmp_dir = tempfile.mkdtemp()
+            try:
+                if run_auth(stream, login, passwd, log=log):
+                    download(stream, n, dest_dir, tmp_dir, log=log)
+            finally:
+                shutil.rmtree(tmp_dir)
+        finally:
+            stream.close()
+    except:
+        log_msg(_("FAILED: unable to connect to server"), log)
+        print traceback.format_exc()
+        result = False
+    if log:
+        log.put(None)
+    return result
+
+def run_check_stats(login, passwd, host, port, cert, log=None):
+    result = True
+    try:
+        stream = init_stream(host, port, cert, log=log)
+        try:
+            if run_auth(stream, login, passwd, log=log):
+                check_stats(stream, log=log)            
+        finally:
+            stream.close()
+    except:
+        log_msg(_("FAILED: unable to connect to server"), log)
+        print traceback.format_exc()
+        result = False
+    if log:
+        log.put(None)
+    return result
+
+def run_download_prim(login, passwd, host, port, cert, n, dest_dir, log=None):
+    result = True
+    try:
+        stream = init_stream(host, port, cert, log=log)
+        try:
+            tmp_dir = tempfile.mkdtemp()
+            try:
+                if run_auth(stream, login, passwd, log=log):
+                    download_prim(stream, n, dest_dir, tmp_dir, log=log)
+            finally:
+                shutil.rmtree(tmp_dir)
+        finally:
+            stream.close()
+    except:
+        log_msg(_("FAILED: unable to connect to server"), log)
+        print traceback.format_exc()
+        result = False
+    if log:
+        log.put(None)
+    return result
+
+def run_checkout(login, passwd, host, port, cert, dest_dir, exts, log=None):
+    result = True
+    try:
+        stream = init_stream(host, port, cert, log=log)
+        try:
+            tmp_dir = tempfile.mkdtemp()
+            try:
+                if run_auth(stream, login, passwd, log=log):
+                    checkout(stream, dest_dir, tmp_dir, exts, log=log)
+            finally:
+                shutil.rmtree(tmp_dir)
+        finally:
+            stream.close()
+    except:
+        log_msg(_("FAILED: unable to connect to server"), log)
+        print traceback.format_exc()
+        result = False
+    if log:
+        log.put(None)
+    return result
+
+def run_return(login, passwd, host, port, cert, files, reason, src_dir, file_exts, log=None):
+    result = True
+    try:
+        stream = init_stream(host, port, cert, log=log)
+        try:
+            if run_auth(stream, login, passwd, log=log):
+                return_files(stream, files, reason, src_dir, file_exts, log)            
+        finally:
+            stream.close()
+    except:
+        log_msg(_("FAILED: unable to connect to server"), log)
+        print traceback.format_exc()
+        result = False
+    if log:
+        log.put(None)
+    return result
+
+def log_msg(msg, log, new_line=True):
+    if new_line:
+        msg = msg + "\n"
+    sys.stdout.write(msg.encode('utf-8'))
+    if log:
+        log.put(msg)
+
+if __name__ == "__main__":
+
+    optparser = OptionParser(usage="""usage: %prog [options] CMD CMD-ARGS
+
+    Command line client for files distribution system.""")
+
+    optparser.add_option("--login", dest="login")
+    optparser.add_option("--passwd", dest="passwd")
+    optparser.add_option("--home", metavar="DIR", dest="home",
+            help="Directory with users files.")
+
+    (options, args) = optparser.parse_args()
+
+    if options.login == None:
+        optparser.print_help()
+        print "\n--login option is mandatory"
+        sys.exit(0)
+    if options.passwd == None:
+        optparser.print_help()
+        print "\n--passwd option is mandatory"
+        sys.exit(0)
+    if options.home == None:
+        optparser.print_help()
+        print "\n--home option is mandatory"
+        sys.exit(0)
+
+    commands = ["upload", "download", "download2"]
+    if len(args) < 1 or args[0] not in commands:
+        optparser.print_help()
+        print "\nCMD should be one of the following:"
+        print commands
+        sys.exit(0)
+
+    cmd = args[0]
+    if cmd == "upload":
+        run_upload(options.login, options.passwd, args[1:], options.home)
+    elif cmd == "download":
+        run_download(options.login, options.passwd,
+                int(args[1]), options.home)
+    elif cmd == "download2":
+        run_download_prim(options.login, options.passwd,
+                int(args[1]), options.home)
diff --git a/gui/const.py b/gui/const.py
new file mode 100644
index 0000000..379be81
--- /dev/null
+++ b/gui/const.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+import os
+
+from dfs.config import Config
+
+# Global configuration file
+global_config_name = "data/global.cfg"
+_cfg = Config(global_config_name)
+
+# File with list of directories
+dir_cache_name = _cfg["general.dir_cache"]
+
+# File with directory configuration
+config_name = _cfg["general.config"]
+# File with file names marked as ready
+ready_name = _cfg["general.ready"]
+# File with files footprints
+footprints_name = _cfg["general.footprints"]
+# Default file extensions
+default_exts = _cfg["general.default_exts"].split()
+# Language (pl or en)
+lang = _cfg["general.lang"]
+# option to hide dir menu if precofigured
+hide_dir_menu = _cfg["general.hide_dir_menu"]
+
+program = _cfg["general.program"]
+
+extension = _cfg["general.extension"]
+
+conn_host = _cfg["connection.host"]
+conn_port = int(_cfg["connection.port"])
+conn_cert = _cfg["connection.cert"]
+
+# Path to directory-specific configuration files 
+def path(p, name):
+    return os.path.join(p, name)
diff --git a/gui/data/.dir.lst b/gui/data/.dir.lst
new file mode 100644
index 0000000..2284183
--- /dev/null
+++ b/gui/data/.dir.lst
@@ -0,0 +1,5 @@
+/home/me2/workspace/dist-system/data/foldery_anotatorow/ann1
+/home/me2/workspace/dist-system/data/foldery_anotatorow
+/home/me2/workspace/core/dist-system/data/annotators_folders/ann2
+/home/me2/workspace/core/dist-system/data/annotators_folders/ann1
+/home/me2/workspace/core/dist-system/data/annotators_folders/adj1
diff --git a/gui/data/cert.pem b/gui/data/cert.pem
new file mode 100644
index 0000000..0dd2101
--- /dev/null
+++ b/gui/data/cert.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICkDCCAfmgAwIBAgIJAJPcESSEV86nMA0GCSqGSIb3DQEBBQUAMGExCzAJBgNV
+BAYTAlBMMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdD
+b21wYW55MSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlQGV4YW1wbGUuY29tMB4XDTEz
+MDEyMzEyMjIwN1oXDTE0MDEyMzEyMjIwN1owYTELMAkGA1UEBhMCUEwxDTALBgNV
+BAgMBFRlc3QxDTALBgNVBAcMBENpdHkxEDAOBgNVBAoMB0NvbXBhbnkxIjAgBgkq
+hkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
+gY0AMIGJAoGBAKUG9QHjUiCSjcvFQl1OqUgZ8c4Vzm7m7Ua7D33YDnGo3QGuhPML
+0JB+JPBG++MrPdQqMV8eeXe1dFlrm6SHnrEZIhwZg8iDnPhFKNjHiAkx0PCQdI93
+rAe4EcszkgbQCXXLGjQJNDikwGZ6ciylJ7C2XewWTlWjv7/LqMwhQotLAgMBAAGj
+UDBOMB0GA1UdDgQWBBRLRvOFIo9LIeiT2ohQalEzy687ijAfBgNVHSMEGDAWgBRL
+RvOFIo9LIeiT2ohQalEzy687ijAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA
+A4GBAD1odtgH1kZ4a0TEbwjK2PaHTYyofkkgeXsGGnG3eMkwKQ7PHJarBl7GtxP5
+Ls91k/KvBuNqCM/lPmM94USDSXrKGe0Zzc9WZbDG9SXO23gGOwPl+Tg9v1ocRHd2
+dYYIgZM8/tLcsHyNNMd7c4BOMD4rzkjgbThFFMl/2NSkvrO7
+-----END CERTIFICATE-----
diff --git a/gui/data/global.cfg b/gui/data/global.cfg
new file mode 100644
index 0000000..e225ffc
--- /dev/null
+++ b/gui/data/global.cfg
@@ -0,0 +1,21 @@
+[general]
+
+config = .dir.cfg
+ready = .ready.lst
+footprints = .footprints.lst
+default_exts = .mmax _mentions.xml _words.xml
+lang = pl
+hide_dir_menu = no
+program = mmax
+extension = .mmax
+
+{paths}
+dir_cache = .dir.lst
+
+[connection]
+
+host = localhost
+port = 2225
+
+{paths}
+cert = cert.pem
diff --git a/gui/dfs.wxg b/gui/dfs.wxg
new file mode 100644
index 0000000..1f554a0
--- /dev/null
+++ b/gui/dfs.wxg
@@ -0,0 +1,367 @@
+<?xml version="1.0"?>
+<!-- generated by wxGlade 0.6.3 on Fri Sep 30 18:28:38 2011 -->
+
+<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">
+    <object class="MainFrame" name="main_frame" base="EditFrame">
+        <style>wxDEFAULT_FRAME_STYLE</style>
+        <title>--</title>
+        <menubar>1</menubar>
+        <centered>1</centered>
+        <focused>1</focused>
+        <size>663, 556</size>
+        <object class="wxMenuBar" name="frame_1_menubar" base="EditMenuBar">
+            <menus>
+                <menu name="dir_menu" label="Directory">
+                    <item>
+                        <label>Open</label>
+                        <id>5</id>
+                        <name>dir_menu_open</name>
+                        <help_str>Open new directory</help_str>
+                        <handler>open_directory</handler>
+                    </item>
+                    <item>
+                        <label>Reconfigure</label>
+                        <id>6</id>
+                        <help_str>Reconfigure directory</help_str>
+                        <handler>reconf_directory</handler>
+                    </item>
+                    <item>
+                        <label>---</label>
+                        <id>---</id>
+                        <name>---</name>
+                    </item>
+                </menu>
+                <menu name="" label="Files">
+                    <item>
+                        <label>Upload Selected</label>
+                        <id>1</id>
+                        <help_str>Upload annotated or adjudicated files</help_str>
+                        <handler>upload</handler>
+                    </item>
+                    <item>
+                        <label>Checkin Selected</label>
+                        <id>8</id>
+                        <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>
+                        <handler>checkin</handler>
+                    </item>
+                    <item>
+                        <label>Download (annotation)</label>
+                        <id>2</id>
+                        <help_str>Download files for annotation</help_str>
+                        <handler>download</handler>
+                    </item>
+                    <item>
+                        <label>Download (adjudication)</label>
+                        <id>3</id>
+                        <help_str>Download files for adjudication</help_str>
+                        <handler>download_prim</handler>
+                    </item>
+                    <item>
+                        <label>Checkout</label>
+                        <id>7</id>
+                        <help_str>Checkout files from server to your local, empty directory</help_str>
+                        <handler>checkout</handler>
+                    </item>
+                </menu>
+                <menu name="" label="Select">
+                    <item>
+                        <label>Ready</label>
+                        <id>4</id>
+                        <help_str>Select items which are marked for upload</help_str>
+                        <handler>select_ready</handler>
+                    </item>
+                    <item>
+                        <label>Modified</label>
+                        <id>9</id>
+                        <help_str>Select items which are modified</help_str>
+                        <handler>select_modified</handler>
+                    </item>
+                </menu>
+                <menu name="" label="Help">
+                </menu>
+            </menus>
+        </object>
+        <object class="wxBoxSizer" name="sizer_6" base="EditBoxSizer">
+            <orient>wxHORIZONTAL</orient>
+            <object class="sizeritem">
+                <flag>wxEXPAND</flag>
+                <border>0</border>
+                <option>1</option>
+                <object class="wxSplitterWindow" name="window_1" base="EditSplitterWindow">
+                    <style>wxSP_3D|wxSP_BORDER</style>
+                    <orientation>wxSPLIT_HORIZONTAL</orientation>
+                    <sash_pos>-100</sash_pos>
+                    <window_2>window_1_pane_2</window_2>
+                    <window_1>window_1_pane_1</window_1>
+                    <object class="wxPanel" name="window_1_pane_1" base="EditPanel">
+                        <style>wxTAB_TRAVERSAL</style>
+                        <object class="wxBoxSizer" name="sizer_1" base="EditBoxSizer">
+                            <orient>wxVERTICAL</orient>
+                            <object class="sizeritem">
+                                <flag>wxEXPAND</flag>
+                                <border>0</border>
+                                <option>1</option>
+                                <object class="wxNotebook" name="notebook_1" base="EditNotebook">
+                                    <style>0</style>
+                                    <tabs>
+                                        <tab window="notebook_1_pane_1">annotation</tab>
+                                        <tab window="notebook_1_pane_2">adjudication</tab>
+                                    </tabs>
+                                    <object class="wxPanel" name="notebook_1_pane_1" base="EditPanel">
+                                        <style>wxTAB_TRAVERSAL</style>
+                                        <object class="wxBoxSizer" name="sizer_2" base="EditBoxSizer">
+                                            <orient>wxVERTICAL</orient>
+                                            <object class="sizeritem">
+                                                <flag>wxEXPAND</flag>
+                                                <border>0</border>
+                                                <option>1</option>
+                                                <object class="wxListCtrl" name="files_list" base="EditListCtrl">
+                                                    <style>wxLC_REPORT|wxSIMPLE_BORDER</style>
+                                                    <events>
+                                                        <handler event="EVT_LIST_ITEM_ACTIVATED">activate_item</handler>
+                                                    </events>
+                                                </object>
+                                            </object>
+                                        </object>
+                                    </object>
+                                    <object class="wxPanel" name="notebook_1_pane_2" base="EditPanel">
+                                        <style>wxTAB_TRAVERSAL</style>
+                                        <object class="wxBoxSizer" name="sizer_3" base="EditBoxSizer">
+                                            <orient>wxVERTICAL</orient>
+                                            <object class="sizeritem">
+                                                <flag>wxEXPAND</flag>
+                                                <border>0</border>
+                                                <option>1</option>
+                                                <object class="wxListCtrl" name="super_files_list" base="EditListCtrl">
+                                                    <style>wxLC_REPORT|wxSIMPLE_BORDER</style>
+                                                    <events>
+                                                        <handler event="EVT_LIST_ITEM_ACTIVATED">activate_super_item</handler>
+                                                    </events>
+                                                </object>
+                                            </object>
+                                        </object>
+                                    </object>
+                                </object>
+                            </object>
+                        </object>
+                    </object>
+                    <object class="wxPanel" name="window_1_pane_2" base="EditPanel">
+                        <style>wxTAB_TRAVERSAL</style>
+                        <object class="wxBoxSizer" name="sizer_7" base="EditBoxSizer">
+                            <orient>wxVERTICAL</orient>
+                            <object class="sizeritem">
+                                <flag>wxEXPAND</flag>
+                                <border>0</border>
+                                <option>1</option>
+                                <object class="wxTextCtrl" name="log_ctrl" base="EditTextCtrl">
+                                    <style>wxTE_MULTILINE|wxTE_READONLY</style>
+                                </object>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+            </object>
+        </object>
+    </object>
+    <object class="LoginDialog" name="login_dialog" base="EditDialog">
+        <style>wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER|wxTHICK_FRAME</style>
+        <title>login</title>
+        <object class="wxBoxSizer" name="sizer_4" base="EditBoxSizer">
+            <orient>wxVERTICAL</orient>
+            <object class="sizeritem">
+                <border>0</border>
+                <option>0</option>
+                <object class="wxGridSizer" name="grid_sizer_1" base="EditGridSizer">
+                    <hgap>0</hgap>
+                    <rows>2</rows>
+                    <cols>2</cols>
+                    <vgap>0</vgap>
+                    <object class="sizeritem">
+                        <flag>wxALIGN_CENTER_VERTICAL</flag>
+                        <border>0</border>
+                        <option>0</option>
+                        <object class="wxStaticText" name="label_1" base="EditStaticText">
+                            <attribute>1</attribute>
+                            <label>login: </label>
+                        </object>
+                    </object>
+                    <object class="sizeritem">
+                        <border>0</border>
+                        <option>0</option>
+                        <object class="wxTextCtrl" name="login_ctrl" base="EditTextCtrl">
+                        </object>
+                    </object>
+                    <object class="sizeritem">
+                        <flag>wxALIGN_CENTER_VERTICAL</flag>
+                        <border>0</border>
+                        <option>0</option>
+                        <object class="wxStaticText" name="label_3" base="EditStaticText">
+                            <attribute>1</attribute>
+                            <label>password: </label>
+                        </object>
+                    </object>
+                    <object class="sizeritem">
+                        <border>0</border>
+                        <option>0</option>
+                        <object class="wxTextCtrl" name="passwd_ctrl" base="EditTextCtrl">
+                            <style>wxTE_PASSWORD</style>
+                        </object>
+                    </object>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <border>0</border>
+                <option>0</option>
+                <object class="wxCheckBox" name="save_passwd" base="EditCheckBox">
+                    <label>save password</label>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <flag>wxALIGN_CENTER_HORIZONTAL</flag>
+                <border>0</border>
+                <option>0</option>
+                <object class="wxButton" name="button_1" base="EditButton">
+                    <label>OK</label>
+                    <id>wx.ID_OK</id>
+                    <events>
+                        <handler event="EVT_BUTTON">on_ok</handler>
+                    </events>
+                    <size>85, 32</size>
+                </object>
+            </object>
+        </object>
+    </object>
+    <object class="DownloadNumberDialog" name="download_number" base="EditDialog">
+        <style>wxDEFAULT_DIALOG_STYLE</style>
+        <title>checkout</title>
+        <centered>1</centered>
+        <object class="wxGridSizer" name="grid_sizer_2" base="EditGridSizer">
+            <hgap>0</hgap>
+            <rows>2</rows>
+            <cols>2</cols>
+            <vgap>0</vgap>
+            <object class="sizeritem">
+                <flag>wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL</flag>
+                <border>0</border>
+                <option>0</option>
+                <object class="wxStaticText" name="label_4" base="EditStaticText">
+                    <attribute>1</attribute>
+                    <tooltip>Number of files to download</tooltip>
+                    <label>download:</label>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <border>0</border>
+                <option>0</option>
+                <object class="wxSpinCtrl" name="checkout_number" base="EditSpinCtrl">
+                    <range>1, 300</range>
+                    <value>1</value>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <flag>wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL</flag>
+                <border>0</border>
+                <option>0</option>
+                <object class="wxButton" name="button_4" base="EditButton">
+                    <label>OK</label>
+                    <id>wx.ID_YES</id>
+                    <events>
+                        <handler event="EVT_BUTTON">on_ok</handler>
+                    </events>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <flag>wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL</flag>
+                <border>0</border>
+                <option>0</option>
+                <object class="wxButton" name="button_5" base="EditButton">
+                    <label>Cancel</label>
+                    <id>wx.ID_NO</id>
+                    <events>
+                        <handler event="EVT_BUTTON">on_cancel</handler>
+                    </events>
+                </object>
+            </object>
+        </object>
+    </object>
+    <object class="ConfigDialog" name="config_dialog" base="EditDialog">
+        <style>wxDEFAULT_DIALOG_STYLE</style>
+        <title>Directory configuration</title>
+        <object class="wxBoxSizer" name="sizer_5" base="EditBoxSizer">
+            <orient>wxVERTICAL</orient>
+            <object class="sizeritem">
+                <border>0</border>
+                <option>0</option>
+                <object class="wxGridSizer" name="grid_sizer_4" base="EditGridSizer">
+                    <hgap>0</hgap>
+                    <rows>3</rows>
+                    <cols>2</cols>
+                    <vgap>0</vgap>
+                    <object class="sizeritem">
+                        <flag>wxALIGN_CENTER_VERTICAL</flag>
+                        <border>0</border>
+                        <option>0</option>
+                        <object class="wxStaticText" name="label_2" base="EditStaticText">
+                            <attribute>1</attribute>
+                            <tooltip>Login supplied by system administrator</tooltip>
+                            <label>login:</label>
+                        </object>
+                    </object>
+                    <object class="sizeritem">
+                        <flag>wxALIGN_CENTER_VERTICAL</flag>
+                        <border>0</border>
+                        <option>0</option>
+                        <object class="wxTextCtrl" name="login_ctrl" base="EditTextCtrl">
+                        </object>
+                    </object>
+                    <object class="sizeritem">
+                        <flag>wxALIGN_CENTER_VERTICAL</flag>
+                        <border>0</border>
+                        <option>0</option>
+                        <object class="wxStaticText" name="label_6" base="EditStaticText">
+                            <attribute>1</attribute>
+                            <tooltip>Password supplied by system administrator</tooltip>
+                            <label>password:</label>
+                        </object>
+                    </object>
+                    <object class="sizeritem">
+                        <flag>wxALIGN_CENTER_VERTICAL</flag>
+                        <border>0</border>
+                        <option>0</option>
+                        <object class="wxTextCtrl" name="passwd_ctrl" base="EditTextCtrl">
+                            <style>wxTE_PASSWORD</style>
+                        </object>
+                    </object>
+                    <object class="sizeritem">
+                        <flag>wxALIGN_CENTER_VERTICAL</flag>
+                        <border>0</border>
+                        <option>0</option>
+                        <object class="wxStaticText" name="label_5" base="EditStaticText">
+                            <attribute>1</attribute>
+                            <tooltip>List of space separated file extensions</tooltip>
+                            <label>extensions:</label>
+                        </object>
+                    </object>
+                    <object class="sizeritem">
+                        <flag>wxALIGN_CENTER_VERTICAL</flag>
+                        <border>0</border>
+                        <option>0</option>
+                        <object class="wxTextCtrl" name="exts_ctrl" base="EditTextCtrl">
+                        </object>
+                    </object>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <flag>wxALIGN_CENTER_HORIZONTAL</flag>
+                <border>0</border>
+                <option>0</option>
+                <object class="wxButton" name="button_2" base="EditButton">
+                    <label>OK</label>
+                    <id>wx.ID_OK</id>
+                    <focused>1</focused>
+                    <size>85, 29</size>
+                </object>
+            </object>
+        </object>
+    </object>
+</application>
diff --git a/gui/dfs/__init__.py b/gui/dfs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gui/dfs/__init__.py
diff --git a/gui/dfs/config.py b/gui/dfs/config.py
new file mode 100644
index 0000000..5a53f9c
--- /dev/null
+++ b/gui/dfs/config.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+import re
+import os
+
+SECTION = re.compile("^\[(.*)\]$")
+SUBSECTION = re.compile("^\{(.*)\}$")
+ENTRY = re.compile("^([^=\s]+)\s*=\s*([^=]+)\s*$")
+
+def cut_comment(s):
+    return s.split("#")[0]
+
+class Config:
+
+    def __init__(self, path):
+        section = None
+        subsection = None
+        self.d = {}
+        with open(path) as f:
+            for line in f:
+                line = cut_comment(line).strip()
+
+                m = SECTION.search(line)
+                if m is not None:
+                    section = m.group(1)
+                    subsection = None
+                    continue
+
+                m = SUBSECTION.search(line)
+                if m is not None:
+                    subsection = m.group(1)
+                    continue
+
+                m = ENTRY.search(line)
+                if m is not None:
+                    key = m.group(1)
+                    val = m.group(2)
+                    if subsection == "paths":
+                        # print path, val, os.path.join(path, val)
+                        val = self.relative_path(val, path)
+                    if section is not None:
+                        self.d[section + "." + key] = val
+                    else:
+                        self.d[key] = val
+
+    def relative_path(self, path, ref):
+        return os.path.join(os.path.dirname(ref), path)
+
+    def __getitem__(self, key):
+        return self.d[key]
diff --git a/gui/dfs/database.py b/gui/dfs/database.py
new file mode 100644
index 0000000..4f4458b
--- /dev/null
+++ b/gui/dfs/database.py
@@ -0,0 +1,381 @@
+# -*- coding: utf-8 -*-
+import time
+from datetime import date, datetime
+from collections import defaultdict
+
+from xml.etree.ElementTree import Element, ElementTree, tostring
+import xml.etree.ElementTree as ET
+
+class Database:
+
+    def __init__(self, db, anno_per_file, from_file=True):
+        if from_file:
+            self.db_name = db
+            self.tree = ET.parse(db)
+            self.root = self.tree.getroot()
+        else:
+            self.db_name = None
+            self.tree = None
+            self.root = ET.fromstring(db)
+            
+        self.anno_per_file = anno_per_file
+        self.make_index()
+        
+    def owned(self, ann_elem):
+        return not self.finished(ann_elem) and not self.returned(ann_elem)
+
+    def finished(self, ann_elem):
+        return ann_elem.find("checkinDate") is not None 
+    
+    def returned(self, ann_elem):
+        return ann_elem.find("return") is not None
+    
+    def get_reason(self, ann_elem):
+        reas = ann_elem.find("return").find("reason").text
+        if reas is None:
+            reas = ""
+        return reas
+        
+    def rejected(self, file_elem):
+        return file_elem.find("rejected") is not None
+    
+    def fixed(self, ann_elem):
+        ret = ann_elem.find("return")
+        if ret is not None and ret.find("fixed") is not None:
+            return True
+        return False
+       
+    def ann_elem_for_user(self, file_elem, user):
+        idx = 0
+        for ann_elem in file_elem.findall("ann"):
+            if ann_elem.find("annName").text == user:                                
+                return ann_elem, idx
+            idx = idx + 1
+        return None, None
+    
+    def sann_elem_for_user(self, file_elem, user):
+        for ann_elem in file_elem.findall("s_ann"):
+            if ann_elem.find("annName").text == user:                                
+                return ann_elem        
+        return None
+    
+    def fixed_for_user(self, file_elem, user, is_adj):
+        if is_adj:
+            ann = self.sann_elem_for_user(file_elem, user)
+        else:
+            ann, idx = self.ann_elem_for_user(file_elem, user)
+        if ann is not None:
+            return self.fixed(ann)        
+        return False        
+    
+    def make_index(self):
+        self.file_index = {}
+        self.ann_owned_index = defaultdict(list)
+        self.adj_owned_index = defaultdict(list)
+
+        for file_elem in self.root:
+            name_elem = file_elem.find("name")
+            if name_elem == None:
+                raise Exception("No name assigned to a file element !")
+            self.file_index[name_elem.text] = file_elem
+            
+            if self.rejected(file_elem):
+                continue
+
+            for ann_elem in file_elem.findall("ann"):
+                if self.owned(ann_elem): 
+                    ann_name = ann_elem.find("annName").text
+                    self.ann_owned_index[ann_name].append(file_elem)
+
+            for ann_elem in file_elem.findall("s_ann"):
+                if self.owned(ann_elem):
+                    ann_name = ann_elem.find("annName").text
+                    self.adj_owned_index[ann_name].append(file_elem)
+
+    def fix(self, ann_elem):
+        if not self.returned(ann_elem):
+            raise Exception("File not returned in database!")
+        if self.fixed(ann_elem):
+            raise Exception("File already fixed in database!")
+        
+        ret = ann_elem.find("return")
+        fixed = ET.SubElement(ret, "fixed")
+    
+    def add(self, file_name):
+        if self.file_index.has_key(file_name):
+            raise Exception("File name already in database !")
+        file_elem = ET.SubElement(self.root, "file")
+        name_elem = ET.SubElement(file_elem, "name")
+        name_elem.text = file_name
+        date_elem = ET.SubElement(file_elem, "addDate")
+        date_elem.text = time.strftime("%c")
+        self.file_index[file_name] = file_elem
+        
+    def remove(self, file_name):
+        if not self.file_index.has_key(file_name):
+            raise Exception("File name not in database !")
+        file_elem = self.file_index[file_name]
+        self.root.remove(file_elem)
+        self.make_index()
+        
+    def reject(self, file_name, reason):
+        if not self.file_index.has_key(file_name):
+            raise Exception("File name not in database !")
+        file_elem = self.file_index[file_name]
+        if self.rejected(file_elem):
+            raise Exception("File already rejected !")
+        rej_elem = ET.SubElement(file_elem, "rejected")
+        rej_elem.text = reason        
+        self.make_index()
+
+    # For prettyprint, used in 'save' method.
+    def indent(self, elem, level=0):
+        i = "\n" + level * "  "
+        if len(elem):
+            if not elem.text or not elem.text.strip():
+                elem.text = i + "  "
+            if not elem.tail or not elem.tail.strip():
+                elem.tail = i
+            for elem in elem:
+                self.indent(elem, level + 1)
+            if not elem.tail or not elem.tail.strip():
+                elem.tail = i
+        else:
+            if level and (not elem.tail or not elem.tail.strip()):
+                elem.tail = i
+
+    def save(self):
+    	if not self.db_name:
+    	    raise Exception("XML not read from file !")
+    
+    	self.indent(self.root)
+    	self.tree.write(self.db_name, encoding="utf-8")
+
+    def upload_prevention(self, file_name, user, is_super):
+        if not self.file_index.has_key(file_name):
+            return "Filename " + file_name.encode('utf-8') + " not known by server."
+        file_elem = self.file_index[file_name]
+        if self.rejected(file_elem):
+            return "File rejected: " + file_name.encode('utf-8')
+        
+        if is_super:                
+            ann_elem = file_elem.find("s_ann")
+            if ann_elem is None:
+                return "No superannotator assigned to file " + file_name.encode('utf-8')
+            
+            if user != ann_elem.find("annName").text:
+                return "Different superannotator assigned to file " + file_name.encode('utf-8')
+            
+            if ann_elem.find("checkinDate") is not None:
+                return "File " + file_name.encode('utf-8') + " already checked in!"
+            
+            return None
+                        
+        else:            
+            annotations = file_elem.findall("ann")
+            owners = map(lambda ann: ann.find("annName").text, annotations)
+            if not user in owners:
+                return "User " + user + " doesn't own the file " + file_name.encode('utf-8')
+            
+            i = owners.index(user)
+            ann_elem = annotations[i]
+            if ann_elem.find("checkinDate") is not None:
+                return "User " + user + " already checked in the file " + file_name.encode('utf-8')
+            
+            return None
+            
+    def upload_dest(self, file_name, user):
+        """Upload destination (annName element and ID -- 1 or 2) directory."""
+        file_elem = self.file_index[file_name]
+        annotations = file_elem.findall("ann")
+
+        owners = map(lambda ann: ann.find("annName").text, annotations)
+        exc = Exception("Cannot upload " + file_name.encode('utf-8')
+                       + " file by " + user + " annotator !")
+        if not user in owners:
+            raise exc
+
+        i = owners.index(user)
+        ann_elem = annotations[i]
+        if ann_elem.find("checkinDate") is not None:
+            raise exc
+
+        return (ann_elem, i)
+
+    def upload_id(self, file_name, user):
+        return self.upload_dest(file_name, user)[1]
+
+    def return_file(self, file_name, user, reason):        
+        (ann_elem, i) = self.upload_dest(file_name, user)
+        ret_elem = ET.SubElement(ann_elem, "return")        
+        date_elem = ET.SubElement(ret_elem, "date")        
+        date_elem.text = time.strftime("%c")
+        date_elem = ET.SubElement(ret_elem, "reason")
+        if reason is None:
+            reason = ""
+        date_elem.text = reason
+        return i
+    
+    def return_file_prim(self, file_name, user, reason):        
+        file_elem = self.file_index[file_name]
+        ann_elem = file_elem.find("s_ann")
+
+        if (user != ann_elem.find("annName").text or
+             ann_elem.find("checkinDate") is not None):
+            raise Exception("Cannot upload " + file_name.encode('utf-8')
+                    + " file by " + user + " annotator !")
+
+        ret_elem = ET.SubElement(ann_elem, "return")        
+        date_elem = ET.SubElement(ret_elem, "date")        
+        date_elem.text = time.strftime("%c")
+        date_elem = ET.SubElement(ret_elem, "reason")
+        if reason is None:
+            reason = ""
+        date_elem.text = reason
+        
+    def upload(self, file_name, user):
+        (ann_elem, i) = self.upload_dest(file_name, user)
+        date_elem = ET.SubElement(ann_elem, "checkinDate")
+        date_elem.text = time.strftime("%c")
+        return i
+
+    def upload_prim(self, file_name, user):
+        file_elem = self.file_index[file_name]
+        ann_elem = file_elem.find("s_ann")
+
+        if (user != ann_elem.find("annName").text or self.finished(ann_elem)):
+            raise Exception("Cannot upload " + file_name.encode('utf-8')
+                    + " file by " + user + " annotator !")
+
+        date_elem = ET.SubElement(ann_elem, "checkinDate")
+        date_elem.text = time.strftime("%c")
+
+    def download(self, file_name, user):
+        file_elem = self.file_index[file_name]
+
+        annotations = file_elem.findall("ann")
+        owners = map(lambda ann: ann.find("annName").text, annotations)
+        
+        if self.fixed_for_user(file_elem, user, False):
+            ann, idx = self.ann_elem_for_user(file_elem, user)
+            date = ann.find("return").find("date").text
+            ann.remove(ann.find("return"))
+            
+            date_elem = ET.SubElement(ann, "returnDate")
+            date_elem.text = date
+                     
+            date_elem = ET.SubElement(ann, "checkoutDate")
+            date_elem.text = time.strftime("%c")
+            
+            return idx
+        
+        else:        
+            if user in owners or len(owners) + 1 > self.anno_per_file:
+                raise Exception("Cannot set " + user + " annotator to '"
+                                    + file_name.encode('utf-8') + "' file !")
+        
+            ann_elem = ET.SubElement(file_elem, "ann")
+            ann_name_elem = ET.SubElement(ann_elem, "annName")
+            ann_name_elem.text = user
+            
+            date_elem = ET.SubElement(ann_elem, "checkoutDate")
+            date_elem.text = time.strftime("%c")
+            
+            return None
+
+    def download_prim(self, file_name, user):
+        file_elem = self.file_index[file_name]
+
+        if self.fixed_for_user(file_elem, user, True):
+            sann = self.sann_elem_for_user(file_elem, user)
+            date = sann.find("return").find("date").text
+            sann.remove(sann.find("return"))
+            
+            date_elem = ET.SubElement(sann, "returnDate")
+            date_elem.text = date
+                       
+            date_elem = ET.SubElement(sann, "checkoutDate")
+            date_elem.text = time.strftime("%c")    
+            
+            return True
+                   
+        else:  
+            if len(file_elem.findall("s_ann")) > 0:
+                raise Exception("Cannot set " + user + " adjudicator to '"
+                        + file_name.encode('utf-8') + "' file !")
+    
+            ann_elem = ET.SubElement(file_elem, "s_ann")
+            ann_name_elem = ET.SubElement(ann_elem, "annName")
+            ann_name_elem.text = user
+            
+            date_elem = ET.SubElement(ann_elem, "checkoutDate")
+            date_elem.text = time.strftime("%c")
+            
+            return False
+
+    def for_annotation(self, user):
+        priority = []
+        result = []    
+        for file_elem in self.root:
+            if self.rejected(file_elem) or file_elem.find("s_ann") is not None:
+                continue
+            
+            anns = file_elem.findall("ann")
+            owners = map(lambda ann: ann.find("annName").text, anns)
+            if len(owners) < self.anno_per_file and user not in owners:
+                result.append(file_elem.find("name").text)
+            
+            if self.fixed_for_user(file_elem, user, False):
+                priority.append(file_elem.find("name").text)                
+                
+        return priority, result
+
+    def for_adjudication(self, user):
+        priority = []        
+        result = []
+        for file_elem in self.root:
+            if self.rejected(file_elem):
+                continue
+            
+            anns = file_elem.findall("ann")
+            owners = map(lambda ann: ann.find("annName").text, anns)
+            checked = map(lambda ann: ann.find("checkinDate") != None, anns)
+            if (len(owners) == self.anno_per_file
+                and file_elem.find("s_ann") is None
+                and user not in owners
+                and all(checked)):
+                result.append(file_elem.find("name").text)
+                
+            if self.fixed_for_user(file_elem, user, True):                    
+                priority.append(file_elem.find("name").text)                
+                
+        return priority, result
+
+    def owns_normal(self, user): 
+        return [ file_elem.find("name").text
+                 for file_elem
+                 in self.ann_owned_index[user] ]
+
+    def owns_super(self, user):
+        return [ file_elem.find("name").text
+                 for file_elem
+                 in self.adj_owned_index[user] ]
+
+    def owns(self, user):
+        return self.owns_normal(user) + self.owns_super(user)
+    
+    def finished_count(self, user):
+        n = 0
+        for file_elem in self.root:
+                 
+            if self.rejected(file_elem):
+                continue
+            
+            items = file_elem.findall("ann")
+            items.extend(file_elem.findall("s_ann"))
+            for ann_elem in items:
+                ann_name = ann_elem.find("annName").text
+                if ann_name == user and self.finished(ann_elem) and not self.returned(ann_elem):                
+                    n += 1
+                                        
+        return n
diff --git a/gui/dfs/msg/__init__.py b/gui/dfs/msg/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gui/dfs/msg/__init__.py
diff --git a/gui/dfs/msg/message.py b/gui/dfs/msg/message.py
new file mode 100644
index 0000000..295fb11
--- /dev/null
+++ b/gui/dfs/msg/message.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+__all__ = ["decode", "Message", "OkMessage", "KoMessage", "DownloadRequest"
+        , "UploadRequest", "DownloadPrimRequest", "ClientDone", "ServerDone"
+        , "NumMessage", "BoolMessage", "ReturnRequest", "CheckoutRequest",
+        "CheckinRequest", "StatsRequest"]
+
+
+class Message(object):
+
+    """
+    Base class for SSL communication messages.
+    """
+
+    def __init__(self, contents=""):
+        self.contents = contents
+
+    def get_contents(self):
+        return self.contents
+
+    @classmethod
+    def from_contents(cls, contents):
+        return cls(contents)
+
+    def encode(self):
+        """
+        Prepare message to be sent over network.
+        WARNING: Do not override this method.
+        """
+        # Add "-" on the beggining to prevent
+        # message contents from being void.
+        return self.msg_type, "-" + self.get_contents()
+
+
+class NumMessage(Message):
+
+    def __init__(self, n):
+        self.number = n
+
+    def get_contents(self):
+        return str(self.number)
+
+    @classmethod
+    def from_contents(cls, contents):
+        return cls(int(contents))
+
+    def get_number(self):
+        return self.number
+
+
+class BoolMessage(Message):
+
+    def __init__(self, b):
+        self.b = b
+
+    def get_contents(self):
+        return "1" if self.b else "0"
+
+    @classmethod
+    def from_contents(cls, contents):
+        return cls(bool(int(contents)))
+
+    def get_boolean(self):
+        return self.b
+
+class ReturnRequest(NumMessage):
+    pass
+
+class CheckoutRequest(Message):
+    pass
+
+class CheckinRequest(NumMessage):
+    pass
+
+class DownloadRequest(NumMessage):
+    pass
+
+class DownloadPrimRequest(NumMessage):
+    pass
+
+class UploadRequest(NumMessage):
+    pass
+
+class OkMessage(Message):
+    pass
+
+class KoMessage(Message):
+    pass
+
+class StatsRequest(Message):
+    pass
+
+class ServerDone(Message):
+    pass
+
+class ClientDone(Message):
+    pass
+
+
+def subclasses(cls):
+    yield cls
+    for child in cls.__subclasses__():
+        for desc in subclasses(child):
+            yield desc
+
+def decode(msg_type, contents):
+    for cls in subclasses(Message):
+        if cls.msg_type == msg_type:
+            # Discard first, dummy character;
+            # Confront Message.encode method.
+            return cls.from_contents(contents[1:])
+
+for i, cls in enumerate(subclasses(Message)):
+    cls.msg_type = i
diff --git a/gui/dfs/msg/stream.py b/gui/dfs/msg/stream.py
new file mode 100644
index 0000000..116bab0
--- /dev/null
+++ b/gui/dfs/msg/stream.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+import struct
+
+from message import decode
+
+__all__ = ["read_msg", "write_msg", "EndOfStream"]
+
+class EndOfStream(Exception):
+    pass
+
+class BadMessage(Exception):
+    pass
+
+def read_msg(connstream):
+    encoded = read_encoded(connstream)
+    return decode(*encoded)
+
+def accept_msg(connstream, cls):
+    msg = read_msg(connstream)
+    if type(msg) != cls:
+        raise BadMessage("Expected %s, got %s" %
+                (cls.__name__, type(msg).__name__))
+    return msg
+
+def write_msg(connstream, msg):
+    encoded = msg.encode()
+    write_encoded(connstream, *encoded)
+
+def read_encoded(connstream):
+    _type = read_type(connstream)
+    length = read_length(connstream)
+    content = read_content(connstream, length)
+    return _type, content
+
+def write_encoded(connstream, _type, content):
+    write_type(connstream, _type)
+    write_length(connstream, len(content))
+    write_content(connstream, content)
+
+def read_type(connstream):
+    data = read_all(connstream, 1)
+    return struct.unpack("!B", data)[0]
+
+def write_type(connstream, _type):
+    data = struct.pack("!B", _type)
+    write_all(connstream, data)
+
+def read_length(connstream):
+    data = read_all(connstream, 4)
+    return struct.unpack("!L", data)[0]
+
+def write_length(connstream, n):
+    data = struct.pack("!L", n)
+    write_all(connstream, data)
+
+def read_content(connstream, length):
+    return read_all(connstream, length)
+
+def write_content(connstream, content):
+    write_all(connstream, content)
+
+def write_all(connstream, data):
+    k = connstream.write(data)
+    if k != len(data):
+        raise Exception("Did not send all data !")
+
+def read_all(connstream, k):
+    data = ""
+    while k > 0:
+        # part = connstream.read()
+        part = connstream.read(min(k, 1024))
+        if len(part) == 0:
+            raise EndOfStream
+        k -= len(part)
+        # if k < 0:
+        #     raise Exception("Received more than expected !")
+        data += part
+    return data
+
diff --git a/gui/dfs/repo.py b/gui/dfs/repo.py
new file mode 100644
index 0000000..9729984
--- /dev/null
+++ b/gui/dfs/repo.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+import os
+import shutil
+import pysvn
+
+class Repo:
+
+    def __init__(self, path, login, passwd):
+        self.path = path
+        def get_login(_realm, _username, _may_save):
+            return True, login, passwd, False
+        self.svn = pysvn.Client()        
+        self.svn.callback_get_login = get_login
+        self.svn.update(self.path)
+        
+    def add(self, src_path):
+        part_name = os.path.basename(src_path)
+        dest_path = os.path.join(self.new_path(), part_name)
+        
+        if (not self.svn.info(dest_path) is None) or (os.path.exists(dest_path)):
+            raise Exception(src_path+" already present in repository.")
+        
+        shutil.copy(src_path, dest_path)
+        self.svn.add(dest_path)        
+        
+    def remove(self, filename, anno_per_file):
+        paths = set()        
+        paths.add(os.path.join(self.new_path(), filename))
+        for i in range(anno_per_file):
+            paths.add(self.upload_path(filename, i))
+        paths.add(os.path.join(self.finito_path(), filename))
+        
+        removed = []
+        for path in paths:
+            if os.path.exists(path):
+                self.svn.remove(path)
+                removed.append(path)
+                
+        return removed
+
+    def db_path(self):
+        return os.path.join(self.path, "db.xml")
+
+    def new_path(self):
+        return os.path.join(self.path, "new")
+
+    def ann_path(self, idx):
+        ids = chr(idx+ord('A'))
+        tail = os.path.join("annotation", ids)
+        return os.path.join(self.path, tail)
+
+    def finito_path(self):
+        return os.path.join(self.path, "adjudication")
+
+    def upload_path(self, file_name, idx):
+        return os.path.join(self.ann_path(idx), file_name)
+
+    def upload_prim_path(self, file_name):
+        return os.path.join(self.finito_path(), file_name)
+        
+    def read_data(self, path):
+        with open(path) as src:
+            return src.read()
+
+    def read_contents(self, path, exts):
+        contents = {}
+        for ext in exts:
+            contents[ext] = self.read_data(path + ext)
+        return contents
+
+    def write_data(self, path, data):
+        with open(path, "w") as dest:
+            dest.write(data)
+
+    def write_contents(self, path, contents):
+        for ext, data in contents.iteritems():
+            dest_path = path + ext
+            self.write_data(dest_path, data)
+            if self.svn.info(dest_path) is None:
+                self.svn.add(dest_path)
+
+    def upload(self, file_id, idx, contents):
+        self.write_contents(self.upload_path(file_id, idx), contents)
+
+    def upload_prim(self, file_id, contents):        
+        self.write_contents(self.upload_prim_path(file_id), contents)
+
+    def download(self, file_id, exts):
+        return self.read_contents(os.path.join(self.new_path(), file_id), exts)
+
+    def download_prim(self, file_id, exts, anno_per_file):
+        result = []
+        for i in range(anno_per_file):
+            conts = self.read_contents(os.path.join(self.ann_path(i), file_id), exts)
+            result.append(conts)            
+        return result
+
+    def checkout(self, file_id, idx, exts):
+        src_paths = [ self.upload_path(file_id, idx) 
+                    , os.path.join(self.new_path(), file_id) ]
+        for path in src_paths:
+            if all(os.path.exists(path + ext) for ext in exts):
+                return self.read_contents(path, exts)
+ 
+    def checkout_prim(self, file_id, exts):
+        path = os.path.join(self.finito_path(), file_id)
+        if all(os.path.exists(path + ext) for ext in exts):
+            return self.read_contents(path, exts)
+
+    def commit(self, message):
+        self.svn.checkin(self.path, message, recurse=True)
+
+    def revert(self):
+        self.svn.revert(self.path, recurse=True)
diff --git a/gui/dfs/server.py b/gui/dfs/server.py
new file mode 100644
index 0000000..639e5ec
--- /dev/null
+++ b/gui/dfs/server.py
@@ -0,0 +1,401 @@
+# -*- coding: utf-8 -*-
+import sys
+import os
+import traceback
+import socket, ssl
+import random
+from optparse import OptionParser 
+import signal
+
+from utils import validate_user, UserInvalid, send_contents, receive_contents
+from msg.stream import read_msg, write_msg, accept_msg, EndOfStream, BadMessage
+from msg.message import *
+from repo import Repo
+from database import Database
+from config import Config
+
+class SSLServer:
+
+    def __init__(self, host, port, backlog, cert_file, key_file,
+            repository, svn_login, svn_passwd, pass_file,
+            users_file, file_exts, anno_per_file, log=None):
+        """
+        Initialize SSL server.
+
+        params:
+        =======
+        host : str
+            Host name.
+        port : int
+            Port number.
+        backlog : int
+            Maximum number of waiting connections.
+        cert_file : path
+            Public PEM certificate.
+        key_file : path
+            Private key.
+        pass_file : path
+            File with client passwords.
+        users_file : path
+            File with additional users configuration.
+        repository : path
+            Subversion working copy.
+        svn_login : str
+            Subversion login.
+        svn_passwd : str
+            Subversion password.
+        file_exts : [str]
+            List of file extensions.
+        anno_per_file : int
+            Number of normal annotators per file (1 or 2)
+        """
+        self.cert_file = cert_file
+        self.key_file = key_file
+        self.pass_file = pass_file
+        self.users_file = users_file
+        self.wc = Repo(repository, svn_login, svn_passwd)
+        self.file_exts = file_exts
+        if log is not None:
+            self.log = open(log, "a")
+        else:
+            self.log = sys.stdout
+        self.bound = bind_socket(host, port, backlog=backlog)
+        self.serving = None
+        self.anno_per_file = anno_per_file
+
+    def run(self):
+        while True:
+            newsocket, fromaddr = self.bound.accept()
+            self.serving = fromaddr
+            connstream = None
+            try:
+                connstream = ssl.wrap_socket(newsocket,
+                                         server_side=True,
+                                         certfile=self.cert_file,
+                                         keyfile=self.key_file,
+                                         ssl_version=ssl.PROTOCOL_TLSv1)
+            
+                login = validate_user(connstream, self.pass_file)
+                self.serve_client(connstream, login)
+            except UserInvalid as login:
+                print >> self.log, "AUTH ERROR:", login
+            except BadMessage as info:
+                print >> self.log, "BAD MESSAGE ERROR:", info
+                print >> self.log, traceback.format_exc().strip()
+            except EndOfStream:
+                print >> self.log, "UNEXPECTED END OF STREAM:"
+                print >> self.log, traceback.format_exc().strip()
+            except:
+                print >> self.log, "UNEXPECTED ERROR:"
+                print >> self.log, traceback.format_exc().strip()
+            finally:
+                try:
+                    if not connstream is None:
+                        connstream.shutdown(socket.SHUT_RDWR)
+                except:
+                    print >> self.log, "UNEXPECTED ERROR:"
+                    print >> self.log, traceback.format_exc().strip()
+                finally:
+                    if not connstream is None:
+                        connstream.close()
+                    self.serving = None
+
+    def exit(self):
+        if self.serving is not None:
+            print ("Client from %s connected to the server"
+                  % str(self.serving))
+        else:
+            sys.exit(0)
+
+    def process_msg(self, msg, stream, login):
+        if type(msg) == UploadRequest:
+            self.process_upload_msg(msg, stream, login, checkin=False)
+        elif type(msg) == CheckinRequest:
+            self.process_upload_msg(msg, stream, login, checkin=True)
+        elif type(msg) == CheckoutRequest:
+            self.process_checkout_msg(msg, stream, login)
+        elif type(msg) == DownloadRequest:
+            self.process_download_msg(msg, stream, login)
+        elif type(msg) == DownloadPrimRequest:
+            self.process_download_prim_msg(msg, stream, login)
+        elif type(msg) == ReturnRequest:
+            self.process_return_msg(msg, stream, login)
+        elif type(msg) == StatsRequest:
+            self.process_stats_msg(msg, stream, login)      
+        else:
+            print >> self.log, login, "sent:", msg
+
+    def process_return_msg(self, msg, stream, login):
+        reason = accept_msg(stream, Message).get_contents().decode("utf-8")
+        
+        usr_cfg = Config(self.users_file)
+        try:
+            user_adj = login in usr_cfg["auth.adjudicators"].split()
+        except KeyError:
+            user_adj = False
+
+        db = Database(self.wc.db_path(), self.anno_per_file)
+        n = msg.get_number()
+        for _ in range(n):
+            file_id = accept_msg(stream, Message).get_contents()        
+            is_adj = accept_msg(stream, BoolMessage).get_boolean()
+            if is_adj and not user_adj:
+                write_msg(stream, KoMessage())
+                print >> self.log, "Upload error - user " + login + " tried to upload super annotated file " + file_id + " but is not a super annotator"
+                continue
+            else:
+                error = db.upload_prevention(file_id, login, is_adj)
+                if error is not None:
+                    write_msg(stream, KoMessage())
+                    print >> self.log, "Upload error by user " + login + ". Details: "+error                    
+                    continue                    
+            
+            write_msg(stream, OkMessage())            
+            contents = receive_contents(stream)
+            for key in contents.keys():
+                if key not in self.file_exts:
+                    print >> self.log, "Skipped file uploaded by user " + login + ". Filename:"+file_id+key
+                    del contents[key]
+            
+            if is_adj is False:
+                idx = db.return_file(file_id, login, reason)                
+                self.wc.upload(file_id, idx, contents)
+            else:
+                db.return_file_prim(file_id, login, reason)
+                self.wc.upload_prim(file_id, contents)
+
+        db.save()
+        accept_msg(stream, ClientDone)
+        self.wc.commit("return request from %s"
+                      % (login))
+        write_msg(stream, ServerDone())        
+        
+        
+    def process_upload_msg(self, msg, stream, login, checkin=False):
+        usr_cfg = Config(self.users_file)
+        try:
+            user_adj = login in usr_cfg["auth.adjudicators"].split()
+        except KeyError:
+            user_adj = False
+
+        db = Database(self.wc.db_path(), self.anno_per_file)
+        n = msg.get_number()
+        for _ in range(n):
+            file_id = accept_msg(stream, Message).get_contents()
+            is_adj = accept_msg(stream, BoolMessage).get_boolean()
+            if is_adj and not user_adj:
+                write_msg(stream, KoMessage())
+                print >> self.log, "Upload error - user " + login + " tried to upload super annotated file " + file_id + " but is not a super annotator"
+                continue
+            else:
+                error = db.upload_prevention(file_id, login, is_adj)
+                if error is not None:
+                    write_msg(stream, KoMessage())
+                    print >> self.log, "Upload error by user " + login + ". Details: "+error                    
+                    continue                    
+            
+            write_msg(stream, OkMessage())            
+            contents = receive_contents(stream)
+            for key in contents.keys():
+                if key not in self.file_exts:
+                    print >> self.log, "Skipped file uploaded by user " + login + ". Filename:"+file_id+key
+                    del contents[key]
+
+            if is_adj is False:
+                if checkin:
+                    idx = db.upload(file_id, login)
+                else:
+                    idx = db.upload_id(file_id, login)
+                self.wc.upload(file_id, idx, contents)
+            else:
+                if checkin:
+                    db.upload_prim(file_id, login)
+                self.wc.upload_prim(file_id, contents)
+
+        if checkin:
+            db.save()
+        accept_msg(stream, ClientDone)
+        self.wc.commit("%s request from %s"
+                      % ("checkin" if checkin else "upload", login))
+        write_msg(stream, ServerDone())
+
+    def process_checkout_msg(self, msg, stream, login):
+        db = Database(self.wc.db_path(), self.anno_per_file)
+
+        ann_files = db.owns_normal(login)
+        write_msg(stream, NumMessage(len(ann_files)))
+        for file_id in ann_files:
+            write_msg(stream, Message(file_id))
+            idx = db.upload_id(file_id, login)
+            contents = self.wc.checkout(file_id, idx, self.file_exts)
+            send_contents(stream, contents)
+
+        adj_files = db.owns_super(login)
+        write_msg(stream, NumMessage(len(adj_files)))
+        write_msg(stream, NumMessage(self.anno_per_file))
+        for file_id in adj_files:
+            write_msg(stream, Message(file_id))
+
+            contents = self.wc.download_prim(file_id, self.file_exts, self.anno_per_file)
+            for conts in contents:
+                send_contents(stream, conts)
+
+            conts3 = self.wc.checkout_prim(file_id, self.file_exts)
+            if conts3 is None:
+                write_msg(stream, BoolMessage(False))
+            else:
+                write_msg(stream, BoolMessage(True))
+                send_contents(stream, conts3)
+
+        accept_msg(stream, ClientDone)
+        #self.wc.commit("checkout request from %s" % login)
+        write_msg(stream, ServerDone())
+
+    def process_stats_msg(self, msg, stream, login):
+        usr_cfg = Config(self.users_file)
+        db = Database(self.wc.db_path(), self.anno_per_file)
+                
+        write_msg(stream, NumMessage(db.finished_count(login)))
+        
+        accept_msg(stream, ClientDone)        
+        write_msg(stream, ServerDone())
+        
+    def process_download_msg(self, msg, stream, login):
+        usr_cfg = Config(self.users_file)
+        db = Database(self.wc.db_path(), self.anno_per_file)
+
+        down_num = msg.get_number()
+        owns_num = len(db.owns_normal(login))
+        limit = float('inf')
+        try:
+            limit = int(usr_cfg["limits.%s.annotation" % login])
+        except KeyError:
+            limit = int(usr_cfg["limits.annotation"])
+        if down_num + owns_num > limit:
+            down_num = max(0, limit - owns_num)
+
+        for_ann_fixed, for_ann = db.for_annotation(login)
+        if len(for_ann) + len(for_ann_fixed) == 0:
+            print >> self.log, "User " + login + " has no more files to download for annotation."
+        
+        down_files = sample(for_ann_fixed, down_num)
+        down_num = max(0, down_num - len(down_files))
+        down_files = down_files + sample(for_ann, down_num)
+        
+        write_msg(stream, NumMessage(len(down_files)))
+        
+        for file_id in down_files:
+            fixed_idx = db.download(file_id, login)
+            write_msg(stream, Message(file_id))
+            if fixed_idx is None:
+                contents = self.wc.download(file_id, self.file_exts)
+            else:
+                contents = self.wc.checkout(file_id, fixed_idx, self.file_exts)
+            send_contents(stream, contents)
+        db.save()
+        accept_msg(stream, ClientDone)
+        self.wc.commit("download request from %s" % login)
+        write_msg(stream, ServerDone())
+
+    def process_download_prim_msg(self, msg, stream, login):
+        usr_cfg = Config(self.users_file)
+        try:
+            adjudicators = usr_cfg["auth.adjudicators"].split()
+        except KeyError:
+            adjudicators = []
+        if login in adjudicators:
+            write_msg(stream, OkMessage())
+        else:
+            write_msg(stream, KoMessage())
+            return
+        db = Database(self.wc.db_path(), self.anno_per_file)
+
+        down_num = msg.get_number()
+        owns_num = len(db.owns_super(login))
+        limit = float('inf')
+        try:
+            limit = int(usr_cfg["limits.%s.adjudication" % login])
+        except KeyError:
+            limit = int(usr_cfg["limits.adjudication"])
+        if down_num + owns_num > limit:
+            down_num = max(0, limit - owns_num)
+
+        for_adj_fixed, for_adj = db.for_adjudication(login)        
+        if len(for_adj) + len(for_adj_fixed) == 0:
+            print >> self.log, "User " + login + " has no more files to download for superannotation."
+
+        down_files = sample(for_adj_fixed, down_num)        
+        down_num = max(0, down_num - len(down_files))
+        down_files = down_files + sample(for_adj, down_num)
+        
+        write_msg(stream, NumMessage(len(down_files)))
+        write_msg(stream, NumMessage(self.anno_per_file))        
+        for file_id in down_files:            
+            write_msg(stream, Message(file_id))
+            fixed = db.download_prim(file_id, login)
+            
+            contents = self.wc.download_prim(file_id, self.file_exts, self.anno_per_file)
+            for conts in contents:
+                send_contents(stream, conts)
+                
+            if fixed:
+                write_msg(stream, BoolMessage(True))                
+                contents = self.wc.checkout_prim(file_id, self.file_exts)                
+                send_contents(stream, contents)
+            else:
+                write_msg(stream, BoolMessage(False))
+                            
+        db.save()
+        accept_msg(stream, ClientDone)
+        self.wc.commit("download' request from %s" % login)
+        write_msg(stream, ServerDone())
+
+    def serve_client(self, connstream, login):
+        msg = read_msg(connstream)
+        try:
+            self.process_msg(msg, connstream, login)
+        except:
+            self.wc.revert()
+            raise
+
+def bind_socket(host, port, backlog=5):
+    bound = socket.socket()
+    bound.bind((host, port))
+    bound.listen(backlog)
+    return bound
+
+def sample(population, n):
+    if len(population) > n:
+        return random.sample(population, n)
+    else:
+        return population
+
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog CONFIG""")
+    (options, args) = optparser.parse_args()
+    if len(args) != 1:
+        optparser.print_help()
+        sys.exit(0)
+
+    cfg = Config(args[0])
+    server = SSLServer(
+            host=cfg["connection.host"],
+            port=int(cfg["connection.port"]),
+            backlog=int(cfg["connection.backlog"]),
+            cert_file=cfg["connection.certfile"],
+            key_file=cfg["connection.keyfile"],
+            repository=cfg["svn.repository"],
+            svn_login=cfg["svn.login"],
+            svn_passwd=cfg["svn.passwd"],
+            pass_file=cfg["users.passfile"],
+            users_file=cfg["users.config"],
+            file_exts=cfg["file_extensions"].split(),
+            anno_per_file=int(cfg["anno_per_file"])
+            )
+    
+    def handler(signum, frame):
+        server.exit()
+        
+    signal.signal(signal.SIGINT, handler)
+
+    server.run()
diff --git a/gui/dfs/utils.py b/gui/dfs/utils.py
new file mode 100644
index 0000000..e8a6d88
--- /dev/null
+++ b/gui/dfs/utils.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+import hashlib
+
+from msg.stream import accept_msg, write_msg
+from msg.message import Message, OkMessage, KoMessage, NumMessage
+
+class UserInvalid(Exception):
+    pass
+
+def validate_user(conns, pass_file):
+    pass_dict = parse_passwd(pass_file)
+    login = accept_msg(conns, Message).get_contents()
+    passwd = accept_msg(conns, Message).get_contents()
+    if pass_dict.has_key(login) and pass_dict[login] == passwd:
+        write_msg(conns, OkMessage())
+        return login
+    else:
+        write_msg(conns, KoMessage())
+        raise UserInvalid(login)
+
+def parse_passwd(pass_file):
+    result = {}
+    with open(pass_file) as f:
+        for line in f:
+            login, passwd = line.split("=")
+            login = login.strip()
+            passwd = passwd.strip()
+            result[login] = passwd
+    return result
+
+def _check_sum(*args):
+    m = hashlib.sha256()
+    for arg in args:
+        m.update(arg)
+    return m
+
+def check_sum(*args):
+    return _check_sum(*args).digest()
+
+def check_hexsum(*args):
+    return _check_sum(*args).hexdigest()
+
+def send_contents(stream, contents):
+    write_msg(stream, NumMessage(len(contents)))
+    for ext, data in contents.iteritems():
+        write_msg(stream, Message(ext))
+        write_msg(stream, Message(data))
+        write_msg(stream, Message(check_sum(ext, data)))
+        accept_msg(stream, OkMessage)
+
+def receive_contents(stream):
+    contents = {}
+    n = accept_msg(stream, NumMessage).get_number()
+    for _ in range(n):
+        ext = accept_msg(stream, Message).get_contents()
+        data = accept_msg(stream, Message).get_contents()
+        csum = accept_msg(stream, Message).get_contents()
+        if csum == check_sum(ext, data):
+            write_msg(stream, OkMessage())
+        else:
+            write_msg(stream, KoMessage())
+        contents[ext] = data
+    return contents
diff --git a/gui/dir_cache.py b/gui/dir_cache.py
new file mode 100644
index 0000000..02ee649
--- /dev/null
+++ b/gui/dir_cache.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""Directory cache manipulation"""
+
+import os
+
+class DirCache:
+
+    def __init__(self, path, size):
+        if not os.path.exists(path):
+            with open(path, "w") as f: pass
+        self.path = path
+        self.size = size
+
+    def add(self, dir_name):
+        cache = self.get()
+        if dir_name in cache:
+            cache.remove(dir_name)
+        cache.insert(0, dir_name)
+        self.write(cache[:self.size])
+
+    def get(self):
+        with open(self.path) as f:
+            return [x.strip() for x in f.readlines()]
+
+    def write(self, cache):
+        with open(self.path, "w") as f:
+            for entry in cache:
+                print >> f, entry
diff --git a/gui/footprints.py b/gui/footprints.py
new file mode 100644
index 0000000..773900b
--- /dev/null
+++ b/gui/footprints.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+import os
+import copy
+
+# from dfs.utils import check_hexsum as check_sum
+
+def getmtime(path):
+    return round(os.path.getmtime(path))
+
+from utils import all_files
+
+class FootPrints:
+
+    """Class for footprints file manipulation."""
+
+    def __init__(self, prints_path, dir_path, exts):
+        self.prints_path = prints_path
+        self.dir_path = dir_path
+        self.exts = exts
+        if not os.path.exists(prints_path):
+            with open(prints_path, "w") as f: pass
+        self.prints = self.read()
+        self.backup = copy.deepcopy(self.prints)
+
+    def modified(self, name):
+        try:
+            # (mtime, csum) = self.get_print(name)
+            mtime = self.get_print(name)
+        except KeyError:
+            return False
+
+        path = os.path.join(self.dir_path, name)
+        if mtime != self.print_now(path):
+            return True
+        return False
+
+    """ sprawdza czy nie pojawily sie jakies nowe pliki, ewentualnie dodajac dla nich wpisy """
+    def refresh(self):
+        old_prints = self.prints
+        new_prints = {}
+        for name in all_files(self.dir_path, self.exts):
+            if old_prints.has_key(name):
+                new_prints[name] = old_prints[name]
+            else:
+                path = os.path.join(self.dir_path, name)
+                new_prints[name] = self.print_now(path)
+        self.prints = new_prints
+
+    def renew(self, name):
+        path = os.path.join(self.dir_path, name)
+        try:
+            self.prints[name] = self.print_now(path)
+        except:
+            del self.prints[name]
+            print "Footprints: "+ path + " file not found."
+                
+    def renew_all(self):
+        for key in self.prints.keys():
+            self.renew(key)
+
+    def get_print(self, name):
+        path = os.path.join(self.dir_path, name)
+        return self.prints[name]
+
+    def print_now(self, path):        
+        return max(getmtime(path + ext) for ext in self.exts)
+
+    def read(self):
+        prints = {}
+        with open(self.prints_path) as f:
+            for line in f:
+                key, mtime = line.split()
+                prints[key] = float(mtime)
+        return prints
+
+    def write(self):
+        with open(self.prints_path, "w") as f:
+            # for key, (mtime, csum) in sorted(self.prints.items()):
+            #     print >> f, key, "=", mtime, csum
+            for key, mtime in sorted(self.prints.items()):
+                print >> f, key, mtime
+
+    def save(self):
+        if self.prints != self.backup:
+            self.write()
diff --git a/gui/i18n.py b/gui/i18n.py
new file mode 100644
index 0000000..cda2011
--- /dev/null
+++ b/gui/i18n.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+import os, sys
+import locale
+import gettext
+import const
+
+def get_path():
+    '''Get the path to this script no matter how it's run.'''
+    #Determine if the application is a py/pyw or a frozen exe.
+    if hasattr(sys, 'frozen'):
+        # If run from exe
+        dir_path = os.path.dirname(sys.executable)
+    elif '__file__' in locals():
+        # If run from py
+        dir_path = os.path.dirname(__file__)
+    else:
+        # If run from command line
+        dir_path = sys.path[0]
+    return dir_path
+
+       
+# Change this variable to your app name!
+#  The translation files will be under 
+#  @LOCALE_DIR@/@LANGUAGE@/LC_MESSAGES/@APP_NAME@.mo
+#
+APP_NAME = "manager"
+# This is ok for maemo. Not sure in a regular desktop:
+APP_DIR = get_path()
+LOCALE_DIR = os.path.join(APP_DIR, 'po')
+
+langs = ["pl"]
+if (const.lang == "en"):
+    langs = ["en"]
+    
+# Lets tell those details to gettext
+gettext.install (True)
+gettext.bindtextdomain (APP_NAME,
+                        LOCALE_DIR)
+gettext.textdomain (APP_NAME)
+language = gettext.translation (APP_NAME,
+                                LOCALE_DIR,
+                                languages = langs,
+                                fallback = False)
+
+    
\ No newline at end of file
diff --git a/gui/img/down.png b/gui/img/down.png
new file mode 100644
index 0000000..e54d773
Binary files /dev/null and b/gui/img/down.png differ
diff --git a/gui/img/ready.png b/gui/img/ready.png
new file mode 100644
index 0000000..4e60989
Binary files /dev/null and b/gui/img/ready.png differ
diff --git a/gui/img/textimg_1.png b/gui/img/textimg_1.png
new file mode 100644
index 0000000..b77bd57
Binary files /dev/null and b/gui/img/textimg_1.png differ
diff --git a/gui/img/textimg_2.png b/gui/img/textimg_2.png
new file mode 100644
index 0000000..6bfcc34
Binary files /dev/null and b/gui/img/textimg_2.png differ
diff --git a/gui/img/textimg_3.png b/gui/img/textimg_3.png
new file mode 100644
index 0000000..1e64793
Binary files /dev/null and b/gui/img/textimg_3.png differ
diff --git a/gui/img/up.png b/gui/img/up.png
new file mode 100644
index 0000000..7e20039
Binary files /dev/null and b/gui/img/up.png differ
diff --git a/gui/log_thread.py b/gui/log_thread.py
new file mode 100644
index 0000000..7cd6767
--- /dev/null
+++ b/gui/log_thread.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+import wx
+from Queue import Queue
+from threading import Thread
+import copy
+
+class RThread(Thread):
+
+    def __init__(self, target, args, kwargs):
+        self.target = target
+        self.args = args
+        self.kwargs = kwargs
+        self.result = None
+        Thread.__init__(self)
+
+    def run(self):
+        self.result = self.target(*self.args, **self.kwargs)
+
+class LogThread(Thread):
+
+    def __init__(self, text_ctrl, target, on_end=None, args=(), kwargs={}):
+        self.text_ctrl =  text_ctrl
+        self.target = target
+        self.on_end = on_end
+        self.args = args
+        self.kwargs = kwargs
+        Thread.__init__(self)
+
+    def run(self):
+        queue = Queue()
+        kwargs = copy.copy(self.kwargs)
+        kwargs["log"] = queue
+        worker = RThread( target=self.target
+                       , args=self.args
+                       , kwargs=kwargs )
+        worker.start()
+        while True:
+            msg = queue.get()
+            if msg is None:
+                break
+            wx.CallAfter(self.print_msg, msg)
+        worker.join()
+
+        if self.on_end:
+            wx.CallAfter(self.on_end, worker.result)
+
+    def print_msg(self, msg):
+        self.text_ctrl.AppendText(msg)
diff --git a/gui/manager.py b/gui/manager.py
new file mode 100755
index 0000000..8b699dc
--- /dev/null
+++ b/gui/manager.py
@@ -0,0 +1,249 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# generated by wxGlade 0.6.3 on Tue Sep 27 12:15:56 2011
+
+import os
+import wx
+
+import i18n
+_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors
+
+from dfs.config import Config
+from client import run_upload, run_download, run_download_prim, run_checkout, run_return, run_check_stats
+
+from MainFrame import MainFrame
+import utils
+import const
+from ready import Ready
+from dir_cache import DirCache
+from log_thread import LogThread
+from footprints import FootPrints
+
+class FileManager(wx.App):
+
+    def OnInit(self):
+        wx.InitAllImageHandlers()
+        self.main_frame = MainFrame(None, -1, "")
+        self.SetTopWindow(self.main_frame)
+        self.main_frame.Show()
+
+        # Custom code
+        self.main_frame.set_manager(self)        
+
+        self.curr_dir = None
+        self.config = None
+        self.selection = None
+        self.connected = False
+        self.dir_cache = DirCache(const.dir_cache_name, size=5)
+        self.refresh_thread = utils.RefreshThread(self, delay=2)
+        self.refresh_thread.start()
+
+        utils.fill_dir_menu(self.main_frame, self.dir_cache.get())
+        # End custom code
+        
+        return 1
+
+    def reconf_directory(self):
+        if not self.curr_dir:
+            return
+        config_path = const.path(self.curr_dir, const.config_name)
+        if utils.create_dir_config( config_path, self.main_frame
+                                  , config=self.config ):
+            self.config = Config(config_path)
+            self.refresh()
+
+    def set_directory(self, path):
+        config_path = const.path(path, const.config_name)
+        if not os.path.exists(config_path):
+            if not utils.create_dir_config(config_path, self.main_frame):
+                return
+        self.config = Config(config_path)
+        self.ready = Ready(const.path(path, const.ready_name))
+        self.curr_dir = path
+        self.dir_cache.add(path)
+        self.main_frame.SetTitle(path)
+        self.refresh()
+
+    def begin_conn(self):
+        if not self.curr_dir:
+            wx.MessageBox(_('Directory not set'), _('Info'))
+            return False
+        if self.connected:
+            wx.MessageBox(_('Another operation running'), _('Info'))
+            return False
+        self.connected = True
+        return True
+
+    def end_conn(self, op_result):
+        self.connected = False
+
+    def upload(self, files, checkin=False):
+        if not self.begin_conn():
+            return
+
+        def on_end(result):
+            self.end_conn(result)
+            if checkin:
+                return
+            if result == True:
+                prints = self.footprints()
+                for file_name in files:
+                    prints.renew(file_name)
+                prints.save()
+
+        logger = LogThread( text_ctrl=self.main_frame.log_ctrl
+                          , target=run_upload
+                          , args=( self.config["login"]
+                                 , self.config["passwd"]
+                                 , const.conn_host
+                                 , const.conn_port
+                                 , const.conn_cert
+                                 , files
+                                 , self.curr_dir
+                                 , self.config["extensions"].split()
+                                 , checkin )
+                          , on_end=on_end )
+        logger.start()
+
+    def download(self, n):
+        if not self.begin_conn():
+            return
+        logger = LogThread( text_ctrl=self.main_frame.log_ctrl
+                          , target=run_download
+                          , args=( self.config["login"]
+                                 , self.config["passwd"]
+                                 , const.conn_host
+                                 , const.conn_port
+                                 , const.conn_cert
+                                 , n
+                                 , self.curr_dir )
+                          , on_end=self.end_conn )
+        logger.start()
+
+    def download_prim(self, n):
+        if not self.begin_conn():
+            return
+        logger = LogThread( text_ctrl=self.main_frame.log_ctrl
+                          , target=run_download_prim
+                          , args=( self.config["login"]
+                                 , self.config["passwd"]
+                                 , const.conn_host
+                                 , const.conn_port
+                                 , const.conn_cert
+                                 , n
+                                 , self.curr_dir )
+                          , on_end=self.end_conn )
+        logger.start()
+
+    def checkout(self):
+        if not self.begin_conn():
+            return
+        
+        def on_end(result):
+            self.end_conn(result)
+            if result == True:
+                prints = self.footprints()
+                files_before = prints.prints.keys()                
+                prints.renew_all()
+                prints.save()
+                
+                
+        logger = LogThread( text_ctrl=self.main_frame.log_ctrl
+                          , target=run_checkout
+                          , args=( self.config["login"]
+                                 , self.config["passwd"]
+                                 , const.conn_host
+                                 , const.conn_port
+                                 , const.conn_cert
+                                 , self.curr_dir
+                                 , self.config["extensions"].split() )
+                          , on_end=on_end )
+        logger.start()
+        
+    def return_files(self, files, reason):
+        if not self.begin_conn():
+            return
+
+        def on_end(result):
+            self.end_conn(result)
+            
+        logger = LogThread( text_ctrl=self.main_frame.log_ctrl
+                          , target=run_return
+                          , args=( self.config["login"]
+                                 , self.config["passwd"]
+                                 , const.conn_host
+                                 , const.conn_port
+                                 , const.conn_cert
+                                 , files
+                                 , reason
+                                 , self.curr_dir
+                                 , self.config["extensions"].split()
+                                 )
+                          , on_end=on_end )
+        logger.start()
+        
+    def footprints(self):
+        return FootPrints( const.path(self.curr_dir, const.footprints_name)
+                         , self.curr_dir
+                         , self.config["extensions"].split() )
+
+    def refresh(self):
+        if self.curr_dir is None:
+            return
+
+        ready = self.ready.get_entries()
+        extensions = self.config["extensions"].split()
+
+        footprints = self.footprints()
+        footprints.refresh()
+
+        data = utils.normal_entries(
+                self.curr_dir,
+                extensions,
+                ready,
+                footprints)
+                        
+    	shown = self.main_frame.files_list.shown_data()
+        
+    	if utils.differ(data, shown):
+    	    print "Data changed !"
+    	    self.main_frame.files_list.show(data)
+            footprints.save()
+            
+        data = utils.super_entries(
+                 self.curr_dir,
+                 extensions,
+                 ready,
+                 footprints)
+            
+    	shown = self.main_frame.super_files_list.shown_data()
+        
+    	if utils.differ(data, shown):
+    	    print "Data changed !"
+    	    self.main_frame.super_files_list.show(data)
+    
+        footprints.save()
+
+    def check_stats(self):
+        if not self.begin_conn():
+            return
+
+        def on_end(result):
+            self.end_conn(result)
+
+        logger = LogThread( text_ctrl=self.main_frame.log_ctrl
+                          , target=run_check_stats
+                          , args=( self.config["login"]
+                                 , self.config["passwd"]
+                                 , const.conn_host
+                                 , const.conn_port
+                                 , const.conn_cert )
+                          , on_end=on_end )
+        logger.start()
+        
+# end of class FileManager
+
+if __name__ == "__main__":
+    manager = FileManager(0)
+    manager.MainLoop()
+    manager.refresh_thread.exitFlag = True
diff --git a/gui/po/en/LC_MESSAGES/manager.po b/gui/po/en/LC_MESSAGES/manager.po
new file mode 100644
index 0000000..8407c9a
--- /dev/null
+++ b/gui/po/en/LC_MESSAGES/manager.po
@@ -0,0 +1,229 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: manager\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-12-14 15:46+0100\n"
+"PO-Revision-Date: \n"
+"Last-Translator: Mateusz Kopeć <m.kopec@ipipan.waw.pl>\n"
+"Language-Team: IPI PAN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Polish\n"
+"X-Poedit-Country: POLAND\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: .././MainFrame.py:40
+msgid "Open"
+msgstr ""
+
+#: .././MainFrame.py:40
+msgid "Open new directory"
+msgstr ""
+
+#: .././MainFrame.py:42
+msgid "Reconfigure"
+msgstr ""
+
+#: .././MainFrame.py:42
+msgid "Reconfigure directory"
+msgstr ""
+
+#: .././MainFrame.py:45
+msgid "Directory"
+msgstr ""
+
+#: .././MainFrame.py:47
+msgid "Upload Selected"
+msgstr ""
+
+#: .././MainFrame.py:47
+msgid "Upload annotated or adjudicated files"
+msgstr ""
+
+#: .././MainFrame.py:48
+msgid "Checkin Selected"
+msgstr ""
+
+#: .././MainFrame.py:48
+msgid "Upload annotated or adjudicated; files will be deleted from local disk and you will not be able to modify them again"
+msgstr ""
+
+#: .././MainFrame.py:50
+msgid "Download (annotation)"
+msgstr ""
+
+#: .././MainFrame.py:50
+msgid "Download files for annotation"
+msgstr ""
+
+#: .././MainFrame.py:51
+msgid "Download (adjudication)"
+msgstr ""
+
+#: .././MainFrame.py:51
+msgid "Download files for adjudication"
+msgstr ""
+
+#: .././MainFrame.py:53
+msgid "Checkout"
+msgstr ""
+
+#: .././MainFrame.py:53
+msgid "Checkout files from server to your local, empty directory"
+msgstr ""
+
+#: .././MainFrame.py:54
+msgid "Files"
+msgstr ""
+
+#: .././MainFrame.py:56
+#: .././SortableListCtrl.py:27
+msgid "Ready"
+msgstr ""
+
+#: .././MainFrame.py:56
+msgid "Select items which are marked for upload"
+msgstr ""
+
+#: .././MainFrame.py:57
+#: .././utils.py:45
+#: .././utils.py:94
+msgid "Modified"
+msgstr ""
+
+#: .././MainFrame.py:57
+msgid "Select items which are modified"
+msgstr ""
+
+#: .././MainFrame.py:58
+msgid "Select"
+msgstr ""
+
+#: .././MainFrame.py:106
+msgid "Annotation"
+msgstr ""
+
+#: .././MainFrame.py:107
+msgid "Adjudication"
+msgstr ""
+
+#: .././MainFrame.py:124
+msgid "Choose a directory"
+msgstr ""
+
+#: .././MainFrame.py:137
+msgid "Do you want to upload the following files?"
+msgstr ""
+
+#: .././MainFrame.py:139
+msgid "Question"
+msgstr ""
+
+#: .././MainFrame.py:192
+msgid "Do you want to checkin the following files?"
+msgstr ""
+
+#: .././MainFrame.py:194
+msgid "Files will be deleted from the local directory and you will not be able to read, write or modify them later."
+msgstr ""
+
+#: .././MainFrame.py:210
+#: .././MainFrame.py:221
+msgid "Starting mmax for text"
+msgstr ""
+
+#: .././MainFrame.py:228
+msgid "Error starting mmax:"
+msgstr ""
+
+#: .././utils.py:46
+#: .././utils.py:82
+#: .././utils.py:90
+#: .././utils.py:122
+msgid "Yes"
+msgstr ""
+
+#: .././utils.py:46
+#: .././utils.py:81
+#: .././utils.py:85
+#: .././utils.py:115
+#: .././utils.py:120
+#: .././utils.py:125
+msgid "No"
+msgstr ""
+
+#: .././manager.py:70
+msgid "Directory not set"
+msgstr ""
+
+#: .././manager.py:70
+#: .././manager.py:73
+msgid "Info"
+msgstr ""
+
+#: .././manager.py:73
+msgid "Another operation running"
+msgstr ""
+
+#: .././ConfigDialog.py:21
+msgid "login"
+msgstr ""
+
+#: .././ConfigDialog.py:23
+msgid "password"
+msgstr ""
+
+#: .././ConfigDialog.py:25
+msgid "extensions"
+msgstr ""
+
+#: .././ConfigDialog.py:27
+#: .././DownloadNumberDialog.py:23
+msgid "OK"
+msgstr ""
+
+#: .././ConfigDialog.py:35
+msgid "Directory configuration"
+msgstr ""
+
+#: .././ConfigDialog.py:36
+msgid "Login supplied by system administrator"
+msgstr ""
+
+#: .././ConfigDialog.py:37
+msgid "Password supplied by system administrator"
+msgstr ""
+
+#: .././ConfigDialog.py:38
+msgid "List of space separated file extensions"
+msgstr ""
+
+#: .././DownloadNumberDialog.py:21
+msgid "Download count"
+msgstr ""
+
+#: .././DownloadNumberDialog.py:24
+msgid "Cancel"
+msgstr ""
+
+#: .././DownloadNumberDialog.py:35
+msgid "Download"
+msgstr ""
+
+#: .././DownloadNumberDialog.py:36
+msgid "Number of files to download"
+msgstr ""
+
+#: .././SortableListCtrl.py:25
+msgid "File"
+msgstr ""
+
+#: .././SortableListCtrl.py:26
+msgid "Status"
+msgstr ""
+
+#: .././SortableListCtrl.py:28
+msgid "Stage"
+msgstr ""
+
diff --git a/gui/po/generate_pot.sh b/gui/po/generate_pot.sh
new file mode 100755
index 0000000..8091007
--- /dev/null
+++ b/gui/po/generate_pot.sh
@@ -0,0 +1 @@
+xgettext --language=Python --keyword=_ --output=manager.pot `find ../. -name "*.py"`
diff --git a/gui/po/manager.pot b/gui/po/manager.pot
new file mode 100644
index 0000000..2df6a9e
--- /dev/null
+++ b/gui/po/manager.pot
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-06-27 16:25+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: .././utils.py:46 .././MainFrame.py:62 .././SortableListCtrl.py:130
+msgid "Modified"
+msgstr ""
+
+#: .././utils.py:47 .././SortableListCtrl.py:78 .././SortableListCtrl.py:106
+#: .././SortableListCtrl.py:124 .././SortableListCtrl.py:127
+msgid "Yes"
+msgstr ""
+
+#: .././utils.py:47 .././SortableListCtrl.py:110
+msgid "No"
+msgstr ""
+
+#: .././DownloadNumberDialog.py:21
+msgid "Download count"
+msgstr ""
+
+#: .././DownloadNumberDialog.py:23 .././ReturnReasonDialog.py:27
+#: .././ConfigDialog.py:27
+msgid "OK"
+msgstr ""
+
+#: .././DownloadNumberDialog.py:24 .././ReturnReasonDialog.py:28
+msgid "Cancel"
+msgstr ""
+
+#: .././DownloadNumberDialog.py:35
+msgid "Download"
+msgstr ""
+
+#: .././DownloadNumberDialog.py:36
+msgid "Number of files to download"
+msgstr ""
+
+#: .././ReturnReasonDialog.py:21
+msgid "Why do you want to return the following files?"
+msgstr ""
+
+#: .././ReturnReasonDialog.py:25
+msgid "Reason"
+msgstr ""
+
+#: .././ReturnReasonDialog.py:39
+msgid "Return files"
+msgstr ""
+
+#: .././ConfigDialog.py:21
+msgid "login"
+msgstr ""
+
+#: .././ConfigDialog.py:23
+msgid "password"
+msgstr ""
+
+#: .././ConfigDialog.py:25
+msgid "extensions"
+msgstr ""
+
+#: .././ConfigDialog.py:35
+msgid "Directory configuration"
+msgstr ""
+
+#: .././ConfigDialog.py:36
+msgid "Login supplied by system administrator"
+msgstr ""
+
+#: .././ConfigDialog.py:37
+msgid "Password supplied by system administrator"
+msgstr ""
+
+#: .././ConfigDialog.py:38
+msgid "List of space separated file extensions"
+msgstr ""
+
+#: .././MainFrame.py:43 .././SortableListCtrl.py:85
+msgid "Open"
+msgstr ""
+
+#: .././MainFrame.py:43
+msgid "Open new directory"
+msgstr ""
+
+#: .././MainFrame.py:45
+msgid "Reconfigure"
+msgstr ""
+
+#: .././MainFrame.py:45
+msgid "Reconfigure directory"
+msgstr ""
+
+#: .././MainFrame.py:48
+msgid "Directory"
+msgstr ""
+
+#: .././MainFrame.py:50
+msgid "Upload Selected"
+msgstr ""
+
+#: .././MainFrame.py:50
+msgid "Upload annotated or adjudicated files"
+msgstr ""
+
+#: .././MainFrame.py:51
+msgid "Checkin Selected"
+msgstr ""
+
+#: .././MainFrame.py:51
+msgid ""
+"Upload annotated or adjudicated; files will be deleted from local disk and "
+"you will not be able to modify them again"
+msgstr ""
+
+#: .././MainFrame.py:52
+msgid "Return Selected"
+msgstr ""
+
+#: .././MainFrame.py:52
+msgid ""
+"Return annotated or adjudicated because they are not suitable for annotation "
+"or contain errors"
+msgstr ""
+
+#: .././MainFrame.py:54
+msgid "Download (annotation)"
+msgstr ""
+
+#: .././MainFrame.py:54
+msgid "Download files for annotation"
+msgstr ""
+
+#: .././MainFrame.py:55
+msgid "Download (adjudication)"
+msgstr ""
+
+#: .././MainFrame.py:55
+msgid "Download files for adjudication"
+msgstr ""
+
+#: .././MainFrame.py:57
+msgid "Checkout"
+msgstr ""
+
+#: .././MainFrame.py:57
+msgid "Checkout files from server to your local, empty directory"
+msgstr ""
+
+#: .././MainFrame.py:59
+msgid "Files"
+msgstr ""
+
+#: .././MainFrame.py:61 .././SortableListCtrl.py:49
+msgid "Ready"
+msgstr ""
+
+#: .././MainFrame.py:61
+msgid "Select items which are marked for upload"
+msgstr ""
+
+#: .././MainFrame.py:62
+msgid "Select items which are modified"
+msgstr ""
+
+#: .././MainFrame.py:63
+msgid "Select"
+msgstr ""
+
+#: .././MainFrame.py:66
+msgid "Finished count"
+msgstr ""
+
+#: .././MainFrame.py:66
+msgid "Check number of finished texts"
+msgstr ""
+
+#: .././MainFrame.py:67 .././MainFrame.py:110
+msgid "Annotation"
+msgstr ""
+
+#: .././MainFrame.py:111
+msgid "Adjudication"
+msgstr ""
+
+#: .././MainFrame.py:128
+msgid "Choose a directory"
+msgstr ""
+
+#: .././MainFrame.py:141
+msgid "Do you want to upload the following files?"
+msgstr ""
+
+#: .././MainFrame.py:143 .././MainFrame.py:212
+msgid "Question"
+msgstr ""
+
+#: .././MainFrame.py:209
+msgid "Do you want to checkin the following files?"
+msgstr ""
+
+#: .././MainFrame.py:211
+msgid ""
+"Files will be deleted from the local directory and you will not be able to "
+"read, write or modify them later."
+msgstr ""
+
+#: .././manager.py:69
+msgid "Directory not set"
+msgstr ""
+
+#: .././manager.py:69 .././manager.py:72
+msgid "Info"
+msgstr ""
+
+#: .././manager.py:72
+msgid "Another operation running"
+msgstr ""
+
+#: .././client.py:23
+msgid "Connecting server..."
+msgstr ""
+
+#: .././client.py:35 .././client.py:87 .././client.py:113 .././client.py:155
+#: .././client.py:164 .././client.py:234
+msgid "Sending request..."
+msgstr ""
+
+#: .././client.py:44 .././client.py:242
+msgid "Uploading"
+msgstr ""
+
+#: .././client.py:57 .././client.py:255
+msgid "Upload failed: server doesn't accept this file from you"
+msgstr ""
+
+#: .././client.py:66 .././client.py:82 .././client.py:108 .././client.py:150
+#: .././client.py:161 .././client.py:226 .././client.py:275
+msgid "Done"
+msgstr ""
+
+#: .././client.py:74 .././client.py:79 .././client.py:267 .././client.py:272
+msgid "Deleting"
+msgstr ""
+
+#: .././client.py:91 .././client.py:124
+msgid "No files to download"
+msgstr ""
+
+#: .././client.py:96 .././client.py:129 .././client.py:171 .././client.py:183
+msgid "Downloading"
+msgstr ""
+
+#: .././client.py:118
+msgid "FAILED: you don't have adjudicator privileges"
+msgstr ""
+
+#: .././client.py:158
+msgid "Number of finished files: "
+msgstr ""
+
+#: .././client.py:202
+msgid "Deleting local files..."
+msgstr ""
+
+#: .././client.py:218
+msgid "Error occured:"
+msgstr ""
+
+#: .././client.py:220
+msgid "Saving dowloaded files..."
+msgstr ""
+
+#: .././client.py:299
+msgid "Authentication..."
+msgstr ""
+
+#: .././client.py:304
+msgid "FAILED: incorrect login or password"
+msgstr ""
+
+#: .././client.py:318 .././client.py:339 .././client.py:356 .././client.py:377
+#: .././client.py:398 .././client.py:415
+msgid "FAILED: unable to connect to server"
+msgstr ""
+
+#: .././SortableListCtrl.py:47
+msgid "File"
+msgstr ""
+
+#: .././SortableListCtrl.py:48
+msgid "Status"
+msgstr ""
+
+#: .././SortableListCtrl.py:50
+msgid "Stage"
+msgstr ""
+
+#: .././SortableListCtrl.py:150
+msgid "Starting "
+msgstr ""
+
+#: .././SortableListCtrl.py:150
+msgid " for text"
+msgstr ""
+
+#: .././SortableListCtrl.py:159
+msgid "Error starting "
+msgstr ""
diff --git a/gui/po/pl/LC_MESSAGES/manager.po b/gui/po/pl/LC_MESSAGES/manager.po
new file mode 100644
index 0000000..51ecbc6
--- /dev/null
+++ b/gui/po/pl/LC_MESSAGES/manager.po
@@ -0,0 +1,354 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: manager\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-06-27 16:25+0200\n"
+"PO-Revision-Date: \n"
+"Last-Translator: Mateusz Kopeć <m.kopec@ipipan.waw.pl>\n"
+"Language-Team: IPI PAN\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Polish\n"
+"X-Poedit-Country: POLAND\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: .././utils.py:46
+#: .././MainFrame.py:62
+#: .././SortableListCtrl.py:130
+msgid "Modified"
+msgstr "Zmienione"
+
+#: .././utils.py:47
+#: .././SortableListCtrl.py:78
+#: .././SortableListCtrl.py:106
+#: .././SortableListCtrl.py:124
+#: .././SortableListCtrl.py:127
+msgid "Yes"
+msgstr "Tak"
+
+#: .././utils.py:47
+#: .././SortableListCtrl.py:110
+msgid "No"
+msgstr "Nie"
+
+#: .././DownloadNumberDialog.py:21
+msgid "Download count"
+msgstr "Liczba tekstów do pobrania"
+
+#: .././DownloadNumberDialog.py:23
+#: .././ReturnReasonDialog.py:27
+#: .././ConfigDialog.py:27
+msgid "OK"
+msgstr "OK"
+
+#: .././DownloadNumberDialog.py:24
+#: .././ReturnReasonDialog.py:28
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: .././DownloadNumberDialog.py:35
+msgid "Download"
+msgstr "Pobieranie"
+
+#: .././DownloadNumberDialog.py:36
+msgid "Number of files to download"
+msgstr "Liczba nowych tekstów do pobrania z serwera"
+
+#: .././ReturnReasonDialog.py:21
+msgid "Why do you want to return the following files?"
+msgstr "Dlaczego chcesz odesłać poniższe teksty?"
+
+#: .././ReturnReasonDialog.py:25
+msgid "Reason"
+msgstr "Przyczyna"
+
+#: .././ReturnReasonDialog.py:39
+msgid "Return files"
+msgstr "Odeślij teksty"
+
+#: .././ConfigDialog.py:21
+msgid "login"
+msgstr "Login"
+
+#: .././ConfigDialog.py:23
+msgid "password"
+msgstr "Hasło"
+
+#: .././ConfigDialog.py:25
+msgid "extensions"
+msgstr "Rozszerzenia"
+
+#: .././ConfigDialog.py:35
+msgid "Directory configuration"
+msgstr "Konfiguracja katalogu"
+
+#: .././ConfigDialog.py:36
+msgid "Login supplied by system administrator"
+msgstr "Login dostarczony przez administratora systemu"
+
+#: .././ConfigDialog.py:37
+msgid "Password supplied by system administrator"
+msgstr "Hasło dostarczone przez administratora systemu"
+
+#: .././ConfigDialog.py:38
+msgid "List of space separated file extensions"
+msgstr "Lista rozszerzeń plików oddzielona spacjami"
+
+#: .././MainFrame.py:43
+#: .././SortableListCtrl.py:85
+msgid "Open"
+msgstr "Otwórz"
+
+#: .././MainFrame.py:43
+msgid "Open new directory"
+msgstr "Otwórz nowy katalog."
+
+#: .././MainFrame.py:45
+msgid "Reconfigure"
+msgstr "Zmień konfigurację"
+
+#: .././MainFrame.py:45
+msgid "Reconfigure directory"
+msgstr "Zmień konfigurację aktualnego katalogu - hasło i login anotatora oraz rozszerzenia plików."
+
+#: .././MainFrame.py:48
+msgid "Directory"
+msgstr "Katalog tekstów"
+
+#: .././MainFrame.py:50
+msgid "Upload Selected"
+msgstr "Zapisz zaznaczone teksty na serwerze"
+
+#: .././MainFrame.py:50
+msgid "Upload annotated or adjudicated files"
+msgstr "Zapisz zaznaczone teksty na serwerze. Nie zostaną usunięte z Twojego komputera."
+
+#: .././MainFrame.py:51
+msgid "Checkin Selected"
+msgstr "Zakończ pracę nad zaznaczonymi tekstami"
+
+#: .././MainFrame.py:51
+msgid "Upload annotated or adjudicated; files will be deleted from local disk and you will not be able to modify them again"
+msgstr "Zakończ pracę nad zaznaczonymi tekstami. Zostaną one usunięte z Twojego komputera."
+
+#: .././MainFrame.py:52
+msgid "Return Selected"
+msgstr "Odeślij zaznaczone teksty"
+
+#: .././MainFrame.py:52
+msgid "Return annotated or adjudicated because they are not suitable for annotation or contain errors"
+msgstr "Odeślij anotowane lub superanotowane teksty ponieważ nie nadają się do anotacji lub zawierają błędy"
+
+#: .././MainFrame.py:54
+msgid "Download (annotation)"
+msgstr "Pobierz nowe teksty (anotacja)"
+
+#: .././MainFrame.py:54
+msgid "Download files for annotation"
+msgstr "Pobierz nowe teksty do anotacji."
+
+#: .././MainFrame.py:55
+msgid "Download (adjudication)"
+msgstr "Pobierz nowe teksty (superanotacja)"
+
+#: .././MainFrame.py:55
+msgid "Download files for adjudication"
+msgstr "Pobierz nowe teksty do superanotacji."
+
+#: .././MainFrame.py:57
+msgid "Checkout"
+msgstr "Odtwórz stan z serwera lokalnie"
+
+#: .././MainFrame.py:57
+msgid "Checkout files from server to your local, empty directory"
+msgstr "Pobiera wszystkie pliki przypisane do Ciebie z serwera. Nadpisuje lokalne zmiany."
+
+#: .././MainFrame.py:59
+msgid "Files"
+msgstr "Teksty"
+
+#: .././MainFrame.py:61
+#: .././SortableListCtrl.py:49
+msgid "Ready"
+msgstr "Zakończone"
+
+#: .././MainFrame.py:61
+msgid "Select items which are marked for upload"
+msgstr "Zaznacz teksty, które są oznaczone jako \"Zakończone\"."
+
+#: .././MainFrame.py:62
+msgid "Select items which are modified"
+msgstr "Zaznacz teksty, które są oznaczone jako \"Zmienione\"."
+
+#: .././MainFrame.py:63
+msgid "Select"
+msgstr "Zaznacz teksty"
+
+#: .././MainFrame.py:66
+msgid "Finished count"
+msgstr "Liczba zakończonych tekstów"
+
+#: .././MainFrame.py:66
+msgid "Check number of finished texts"
+msgstr "Sprawdź liczbę zakończonych tekstów"
+
+#: .././MainFrame.py:67
+#: .././MainFrame.py:110
+msgid "Annotation"
+msgstr "Anotacja"
+
+#: .././MainFrame.py:111
+msgid "Adjudication"
+msgstr "Superanotacja"
+
+#: .././MainFrame.py:128
+msgid "Choose a directory"
+msgstr "Wybierz katalog"
+
+#: .././MainFrame.py:141
+msgid "Do you want to upload the following files?"
+msgstr "Czy chcesz zapisać na serwerze poniższe teksty?"
+
+#: .././MainFrame.py:143
+#: .././MainFrame.py:212
+msgid "Question"
+msgstr "Pytanie"
+
+#: .././MainFrame.py:209
+msgid "Do you want to checkin the following files?"
+msgstr "Czy chcesz zakończyć pracę nad poniższymi tekstami?"
+
+#: .././MainFrame.py:211
+msgid "Files will be deleted from the local directory and you will not be able to read, write or modify them later."
+msgstr "Teksty zostaną skasowane z tego komputera i nie będziesz mógł/mogła ich odczytać ani edytować kiedykolwiek później."
+
+#: .././manager.py:69
+msgid "Directory not set"
+msgstr "Katalog nie został wybrany"
+
+#: .././manager.py:69
+#: .././manager.py:72
+msgid "Info"
+msgstr "Informacja"
+
+#: .././manager.py:72
+msgid "Another operation running"
+msgstr "Inna operacja w toku"
+
+#: .././client.py:23
+msgid "Connecting server..."
+msgstr "Łączenie z serwerem..."
+
+#: .././client.py:35
+#: .././client.py:87
+#: .././client.py:113
+#: .././client.py:155
+#: .././client.py:164
+#: .././client.py:234
+msgid "Sending request..."
+msgstr "Wysyłanie żądania..."
+
+#: .././client.py:44
+#: .././client.py:242
+msgid "Uploading"
+msgstr "Wysyłanie"
+
+#: .././client.py:57
+#: .././client.py:255
+msgid "Upload failed: server doesn't accept this file from you"
+msgstr "Wysyłanie nie powiodło się: serwer nie akceptuje tego tekstu od Ciebie."
+
+#: .././client.py:66
+#: .././client.py:82
+#: .././client.py:108
+#: .././client.py:150
+#: .././client.py:161
+#: .././client.py:226
+#: .././client.py:275
+msgid "Done"
+msgstr "Gotowe."
+
+#: .././client.py:74
+#: .././client.py:79
+#: .././client.py:267
+#: .././client.py:272
+msgid "Deleting"
+msgstr "Usuwanie"
+
+#: .././client.py:91
+#: .././client.py:124
+msgid "No files to download"
+msgstr "Brak nowych tekstów do pobrania z serwera (lub pobrano już maksymalną ilość)."
+
+#: .././client.py:96
+#: .././client.py:129
+#: .././client.py:171
+#: .././client.py:183
+msgid "Downloading"
+msgstr "Pobieranie"
+
+#: .././client.py:118
+msgid "FAILED: you don't have adjudicator privileges"
+msgstr "Nie powiodło się: nie masz uprawnień superanotatora."
+
+#: .././client.py:158
+msgid "Number of finished files: "
+msgstr "Liczba zakończonych tekstów:"
+
+#: .././client.py:202
+msgid "Deleting local files..."
+msgstr "Usuwanie lokalnych tekstów..."
+
+#: .././client.py:218
+msgid "Error occured:"
+msgstr "Wystąpił błąd:"
+
+#: .././client.py:220
+msgid "Saving dowloaded files..."
+msgstr "Zapisywanie pobranych tekstów..."
+
+#: .././client.py:299
+msgid "Authentication..."
+msgstr "Uwierzytelnianie..."
+
+#: .././client.py:304
+msgid "FAILED: incorrect login or password"
+msgstr "Nie powiodło się: nieprawidłowy login lub hasło."
+
+#: .././client.py:318
+#: .././client.py:339
+#: .././client.py:356
+#: .././client.py:377
+#: .././client.py:398
+#: .././client.py:415
+msgid "FAILED: unable to connect to server"
+msgstr "Nie powiodło się: problem z łącznością z serwerem."
+
+#: .././SortableListCtrl.py:47
+msgid "File"
+msgstr "Tekst"
+
+#: .././SortableListCtrl.py:48
+msgid "Status"
+msgstr "Status"
+
+#: .././SortableListCtrl.py:50
+msgid "Stage"
+msgstr "Etap"
+
+#: .././SortableListCtrl.py:150
+msgid "Starting "
+msgstr "Uruchamianie "
+
+#: .././SortableListCtrl.py:150
+msgid " for text"
+msgstr " dla tekstu "
+
+#: .././SortableListCtrl.py:159
+msgid "Error starting "
+msgstr "Błąd podczas uruchamiania "
+
+#~ msgid "Starting mmax for text"
+#~ msgstr "Uruchamianie mmax dla tekstu"
diff --git a/gui/ready.py b/gui/ready.py
new file mode 100644
index 0000000..3273dfa
--- /dev/null
+++ b/gui/ready.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+import os
+
+class Ready:
+    
+    """Class for ready file manipulation."""
+
+    def __init__(self, ready_path):
+        if not os.path.exists(ready_path):
+            with open(ready_path, "w") as f: pass
+        self.ready_path = ready_path
+
+    def _check_file(self, file_name, is_ready):
+        entries = self.get_entries()
+        if is_ready == True:
+            entries.append(file_name)
+        else:
+            entries.remove(file_name)
+        self.write_entries(entries)
+
+    def remove_file(self, file_name):
+        self._check_file(file_name, False)
+
+    def save_file(self, file_name):
+        self._check_file(file_name, True)
+
+#    def remove_super_file(self, file_name):
+#        self._check_file(file_name, False, type="s_checkin")
+#
+#    def save_super_file(self, file_name):
+#        self._check_file(file_name, True, type="s_checkin")
+
+    def get_entries(self):
+        result = []
+        with open(self.ready_path) as inp:
+            result = [x.strip() for x in inp.readlines()]
+        return result
+
+    def write_entries(self, entries):
+        with open(self.ready_path, "w") as out:
+            for entry in entries:
+                print >> out, entry
diff --git a/gui/utils.py b/gui/utils.py
new file mode 100644
index 0000000..1c4f738
--- /dev/null
+++ b/gui/utils.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+import os
+import re
+import wx
+import threading
+import time
+
+import i18n
+_ = i18n.language.ugettext #use ugettext instead of getttext to avoid unicode errors 
+
+import  wx.lib.mixins.listctrl  as  listmix
+
+from ConfigDialog import ConfigDialog
+import const
+
+def match_ext(name, exts):
+    return any(name.endswith(ext) for ext in exts)
+
+def cut_ext(name, exts):
+    return re.sub("(%s)$" % "|".join(exts), "", name)
+
+def normal_files(work_dir, exts):
+    names = set(cut_ext(name, exts)
+                for name in os.listdir(work_dir)
+                if match_ext(name, exts))
+    return sorted(names)
+
+def super_files(work_dir, exts):
+    names = []
+    for name in os.listdir(work_dir):
+        path = os.path.join(work_dir, name)
+        if os.path.isdir(path):
+            local = set(cut_ext(name, exts)
+                        for name in os.listdir(path)
+                        if match_ext(name, exts))
+            names.extend(os.path.join(name, tail) for tail in local)
+    return sorted(names)
+
+def all_files(work_dir, exts):
+    return normal_files(work_dir, exts) + super_files(work_dir, exts)
+
+def get_entries(get_names, work_dir, exts, ready, footprints):
+    names = get_names(work_dir, exts)
+    result = []
+    for file_name in names:
+        status = _("Modified") if footprints.modified(file_name) else "--"
+        is_ready = _("Yes") if file_name in ready else _("No")
+        stage = "--"
+        result.append((file_name, status, is_ready, stage))
+    return result
+
+def normal_entries(work_dir, exts, ready, footprints):
+    return get_entries(normal_files, work_dir, exts, ready, footprints)
+
+def super_entries(work_dir, exts, ready, footprints):
+    return get_entries(super_files, work_dir, exts, ready, footprints)
+    
+def differ(data1, data2):
+    if len(data1) != len(data2):
+        return True
+    d1={}
+    for i in range(len(data1)):
+        d1[data1[i][0]]=data1[i][1:]
+    for i in range(len(data2)):
+        if not d1.has_key(data2[i][0]):
+            return True        
+        if d1[data2[i][0]] != data2[i][1:]:
+            return True
+    return False
+
+def change_ready(ready, file_list, pos):
+    file_name = file_list.GetFilename(pos)
+    if file_list.IsReady(pos):
+        file_list.SetReadyNo(pos)        
+        ready.remove_file(file_name)
+    else:
+        file_list.SetReadyYes(pos)        
+        ready.save_file(file_name)        
+            
+def change_ready_prim(ready, super_file_list, pos):
+    def set_no(dirname):
+        for i in range(super_file_list.GetItemCount()):
+            other_name = super_file_list.GetFilename(i)
+            if os.path.dirname(other_name) == dirname:
+                super_file_list.SetReadyNo(i)
+                if other_name in ready.get_entries():
+                    ready.remove_file(other_name)
+
+    file_name = super_file_list.GetFilename(pos)
+    if super_file_list.IsReady(pos):
+        super_file_list.SetReadyNo(pos)
+        ready.remove_file(file_name)
+    else:
+        set_no(os.path.dirname(file_name))
+        super_file_list.SetReadyYes(pos)
+        ready.save_file(file_name)
+
+def create_dir_config(path, parent, config=None):
+    dialog = ConfigDialog(parent, -1)
+    if config:
+        dialog.login_ctrl.SetValue(config["login"])
+        dialog.passwd_ctrl.SetValue(config["passwd"])
+        dialog.exts_ctrl.SetValue(config["extensions"])
+    else:
+        dialog.exts_ctrl.SetValue(" ".join(const.default_exts))
+    try:
+        if dialog.ShowModal() == wx.ID_OK:
+            login = dialog.login_ctrl.GetValue()
+            password = dialog.passwd_ctrl.GetValue()
+            file_exts = dialog.exts_ctrl.GetValue()
+            with open(path, "w") as conf:
+                print >> conf, "login =", login
+                print >> conf, "passwd =", password
+                print >> conf, "extensions =", file_exts
+            return True
+    finally:
+        dialog.Destroy()
+    return False
+
+def fill_dir_menu(main_frame, cache):
+    dir_menu = main_frame.dir_menu
+    last_dir_set=False
+    for entry in cache:
+        if not os.path.isdir(entry):
+            continue
+        wx_id = wx.NewId()
+        dir_menu.Append(wx_id, entry, kind=wx.ITEM_NORMAL)
+        main_frame.Bind(wx.EVT_MENU, main_frame.open_directory, id=wx_id)
+        if not last_dir_set:
+            cmd = wx.CommandEvent(wx.EVT_MENU.evtType[0])            
+            cmd.SetEventObject(main_frame)
+            cmd.SetId(wx_id)
+            main_frame.GetEventHandler().ProcessEvent(cmd)            
+            last_dir_set = True
+
+# Watek odswieza informacje o zawartosci pliku to-checkin.txt
+# (moze sie zmienic za posrednictwem TrEda lub wyedytowany recznie).
+class RefreshThread (threading.Thread):
+    def __init__(self, manager, delay=2):
+        self.exitFlag = False
+        self.delay = delay
+        self.manager = manager
+        threading.Thread.__init__(self)
+
+    def run(self):
+        while self.exitFlag != True:
+            wx.CallAfter(self.manager.refresh)
+            time.sleep(self.delay)
+
+#        self.mtime = None   # message file 'modified time'
+#        while self.exitFlag != True:
+#            new_mtime = os.stat(self.ready_path).st_mtime
+#            if new_mtime != self.mtime:
+#                wx.CallAfter(self.manager.refresh)
+#                self.mtime = new_mtime
+#            time.sleep(self.delay)
+
diff --git a/installation.sh b/installation.sh
new file mode 100644
index 0000000..2027734
--- /dev/null
+++ b/installation.sh
@@ -0,0 +1,43 @@
+#wersja instalacji dla lokalnego pythona
+
+#subversion
+wget http://subversion.tigris.org/downloads/subversion-1.6.15.tar.gz
+wget http://subversion.tigris.org/downloads/subversion-deps-1.6.15.tar.gz
+tar -xzf subversion-1.6.15.tar.gz
+tar -xzf subversion-deps-1.6.15.tar.gz
+cd subversion-1.6.15
+./configure --prefix=$HOME/subversion --without-apxs --without-berkeley-db --with-ssl LDFLAGS="-L/lib64"
+make
+make install
+
+#python
+wget -c http://python.org/ftp/python/2.7.2/Python-2.7.2.tar.bz2
+tar -jxvf Python-2.7.2.tar.bz2
+cd Python-2.7.2
+./configure --prefix=$HOME/.local
+make
+make install
+echo 'PATH=$PATH:$HOME/.local/bin' >> ~/.bashrc
+source ~/.bashrc
+
+#setuptools ???????????????????
+wget http://pypi.python.org/packages/source/s/setuptools/setuptools-0.6c11.tar.gz#md5=7df2a529a074f613b509fb44feefe74e
+tar -xzf setuptools-0.6c11.tar.gz
+cd setuptools-0.6c11
+$HOME/.local/bin/python2.7 setup.py install
+
+#pysvn
+wget http://pysvn.barrys-emacs.org/source_kits/pysvn-1.7.5.tar.gz
+tar -xzf pysvn-1.7.5.tar.gz
+cd pysvn-1.7.5
+cd Source
+$HOME/.local/bin/python2.7 setup.py configure --svn-inc-dir=$HOME/subversion/include/subversion-1/ --svn-lib-dir=$HOME/subversion/lib/
+cd ..
+$HOME/.local/bin/python2.7 setup.py install
+
+
+
+
+
+
+
diff --git a/scripts/add_files.py b/scripts/add_files.py
new file mode 100644
index 0000000..0f82ea6
--- /dev/null
+++ b/scripts/add_files.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+            
+def add_file(wc, db, file_id, src_paths, anno_per_file):
+    try:
+        db.add(file_id)
+    except Exception as ex:
+        print str(ex)
+        return False
+    
+    print "Adding file "+file_id+":"
+    added = []    
+    for src_path in src_paths:
+        try:
+            wc.add(src_path)
+            print "\t - "+src_path
+            added.append(src_path)
+        except Exception as ex:
+            print "\t error: "+str(ex)
+            for a in added:
+                print "\t\t removing already added "+a
+                for b in wc.remove(added, anno_per_file):
+                    print "\t\t\t "+b    
+                
+            db.remove(file_id)
+            return False
+        
+    return True            
+                        
+def match_ext(path, exts):
+    for ext in exts:
+        if path.endswith(ext):
+            return ext
+
+def path_id(path, ext):
+    _, filename = os.path.split(path)
+    return re.sub("%s$" % ext, "", filename)
+
+def group_paths(paths, exts):
+    result = defaultdict(list)            
+    for path in paths:
+        ext = match_ext(path, exts)
+        if (ext != None):
+            file_id = path_id(path, ext)
+            result[file_id].append(path)
+    return result
+
+def get_rec_paths(paths):
+    result = []
+    for path in paths:
+        if os.path.isdir(path):
+            for dirname, dirnames, filenames in os.walk(path):                
+                for filename in filenames:
+                    result.append(os.path.join(dirname, filename))                
+        else:
+            result.append(path)
+    return result
+
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog [options] CONFIG FILES""")
+    optparser.add_option("--extensions", dest="exts", default=".mmax,_mentions.xml,_words.xml",
+            help="List of comma-separated file extensions")
+    (options, args) = optparser.parse_args()
+    if len(args) < 2:
+        optparser.print_help()
+        sys.exit(0)
+
+    conf_path = args[0]    
+    cfg = Config(conf_path)
+    anno_per_file = int(cfg["anno_per_file"])
+    paths = get_rec_paths(args[1:])
+    files = group_paths(paths, options.exts.split(","))
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), anno_per_file)
+    
+    success = []
+    fail = []
+    for file_id, paths in files.iteritems():
+        if add_file(wc, db, file_id, paths, anno_per_file):
+            success.append(file_id)
+        else:
+            fail.append(file_id)     
+    
+    db.save()           
+    wc.commit("Added files: "+str(success))        
+    
+    print ""
+    if len(success) > 0:
+        print "Added files: "+str(success)
+    if len(fail) > 0:
+        print "Failed to add files: "+str(fail)
+        
\ No newline at end of file
diff --git a/scripts/clusters.py b/scripts/clusters.py
new file mode 100755
index 0000000..aecc1d2
--- /dev/null
+++ b/scripts/clusters.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python
+import re
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+
+def fill(text):
+    return fill_custom(text, 20, " ")
+
+def fill_custom(text, l, sym):
+    to_add = max(0, l - len(text))
+    spaces = ""
+    for i in range(to_add):
+        spaces = spaces + sym
+    return text + spaces
+
+def parse_span(span, words_list):    
+    words = []
+    
+    w = span.split(",")    
+    for fragment in w:
+        f = fragment.split("..")
+        id1 = words_list.index(f[0])
+        id2 = words_list.index(f[-1])        
+        for i in range(id1, id2+1):
+            words.append(words_list[i])
+                
+    return words
+
+def get_context(ids, words_list, size):
+    first = -1
+    last = -1
+    
+    i = 0
+    for i in range(len(words_list)):
+        w = words_list[i]
+        if w in ids:
+            if first == -1:
+                first = i
+            last = i
+
+    first = max(0, first - size)
+    last = min(len(words_list), last + size)
+
+    return words_list[first:last]
+        
+def print_file(path):                
+    word_id_2_orth = {}
+    words_list = [] 
+    w = re.compile("<word.* id=\"(.*?)\".*>(.*?)</word>.*")
+    with open(path + "_words.xml", "r") as f:
+        for line in f.readlines():
+            groups = w.findall(line)
+            if len(groups) == 1:
+                group = groups[0]
+                id = group[0]
+                orth = group[1]
+                word_id_2_orth[id] = orth
+                words_list.append(id)
+    
+    me = re.compile("<markable.*id=\"(.*?)\".*")
+    sp = re.compile(".*span=\"(.*?)\".*")
+    co = re.compile(".*comment=\"(.*?)\".*")
+    mg = re.compile(".*mention_group=\"(.*?)\".*")  
+ 
+    mention_id_2_span = {}
+    mention_id_2_comment = {}          
+    clusters = {}
+    with open(path + "_mentions.xml", "r") as f:
+        for line in f.readlines():
+            groups1 = me.findall(line)
+            groups2 = sp.findall(line)
+            groups3 = co.findall(line)       
+            
+            if len(groups1) == 1 and len(groups2) == 1:            
+                id = groups1[0]
+                span = groups2[0]
+                mention_id_2_span[id] = parse_span(span, words_list)
+                
+                if len(groups3) == 1:
+                    mention_id_2_comment[id] = groups3[0]                
+                                 
+                clu = mg.findall(line)
+                if len(clu) == 1 and clu[0] != "empty" and clu[0] != "":
+                    if clu[0] not in clusters:
+                        clusters[clu[0]] = []                        
+                    clusters[clu[0]].append(id)
+        
+    all_clusters = {}
+    for list in clusters.values():
+        l = ["["+" ".join(word_id_2_orth[wid] for wid in mention_id_2_span[id])+"]" for id in list]
+        length = len(l)
+        if length not in all_clusters:
+            all_clusters[length] = []
+        all_clusters[length].append(l)       
+                             
+    return all_clusters
+
+def merge_dicts(dict1, dict2):
+    for key, val in dict2.iteritems():
+        if key in dict1:
+            dict1[key].extend(val)
+        else:
+            dict1[key] = val
+                    
+def print_clusters(db, min_size):
+    all = {}      
+    for filename, file in db.file_index.iteritems():
+        
+        if db.rejected(file):
+            continue        
+                        
+        sann = file.find("s_ann")
+                
+        if sann is not None and db.finished(sann):            
+            path = wc.upload_prim_path(filename)              
+            clus = print_file(path)
+            merge_dicts(all, clus)
+                
+    for key, val in sorted(all.iteritems()):
+        if key >= min_size:
+            print
+            print "#######", "Dlugosc klastra:", key, "#######"
+            for cl in val:
+                print                
+                print ", ".join(cl)                
+        
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog CONFIG MINCLUSTERSIZE""")
+    (options, args) = optparser.parse_args()
+    if len(args) < 2:
+        optparser.print_help()
+        sys.exit(0)
+        
+    conf_path = args[0]
+    min_size = int(args[1])
+    cfg = Config(conf_path)
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), int(cfg["anno_per_file"]))    
+    
+    print_clusters(db, min_size)
+    
diff --git a/scripts/db_averages_stats.py b/scripts/db_averages_stats.py
new file mode 100755
index 0000000..e06df51
--- /dev/null
+++ b/scripts/db_averages_stats.py
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+import re
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+
+def fill(text):
+    return fill_custom(text, 20, " ")
+
+def fill_custom(text, l, sym):
+    text = text.decode("utf-8")    
+    to_add = max(0, l - len(text))
+    spaces = ""
+    for i in range(to_add):
+        spaces = spaces + sym
+    return (text + spaces).encode("utf-8")
+
+def get_span_size(span, words):    
+    w = span.split(",")
+    size = 0
+    for fragment in w:
+        f = fragment.split("..")
+        id1 = words.index(f[0])
+        id2 = words.index(f[-1])        
+        size = size + id2 - id1 + 1
+                
+    return size
+        
+def count_file_stats(path):
+    words = []
+    id = re.compile("<word.* id=\"(.*?)\".*")
+    with open(path + "_words.xml", "r") as f:
+        for line in f.readlines():
+            groups = id.findall(line)
+            if len(groups) == 1:                
+                ident = groups[0]
+                words.append(ident)                
+        
+    sp = re.compile("<markable.*span=\"(.*?)\".*")
+    mg = re.compile(".*mention_group=\"(.*?)\".*")
+    ni = re.compile(".*near_identity=\"(.*?)\".*") 
+    
+    sets = {}
+    near_id = 0
+    mentions = 0
+    mention_sizes = {}
+    with open(path + "_mentions.xml", "r") as f:
+        for line in f.readlines():
+            groups = sp.findall(line)          
+            if len(groups) == 1:
+                mentions = mentions + 1                
+                span = groups[0]
+                             
+                mention_size = get_span_size(span, words)
+                if mention_size in mention_sizes:
+                    mention_sizes[mention_size] = mention_sizes[mention_size] + 1
+                else: 
+                    mention_sizes[mention_size] = 1 
+                
+                group = mg.findall(line)[0]                
+                near = ni.findall(line)[0]
+                
+                if near != "empty" and near != "":
+                    near_id = near_id + 1
+                    
+                if group != "empty" and group != "":                    
+                    if group in sets:
+                        sets[group] = sets[group] + 1
+                    else:       
+                        sets[group] = 1
+    
+    mg_sizes = {}
+    for key, val in sets.iteritems():
+        if val in mg_sizes:
+            mg_sizes[val] = mg_sizes[val] + 1
+        else:
+            mg_sizes[val] = 1
+            
+    return len(words), mention_sizes, mg_sizes, near_id
+
+def merge_dicts(dict1, dict2):
+    
+    for key, val in dict2.iteritems():
+        if key in dict1:
+            dict1[key] = dict1[key] + val
+        else:
+            dict1[key] = val
+
+def get_text_types(db, used_path, mapping_path):    
+    names = []
+    for filename, file in db.file_index.iteritems():
+        if db.rejected(file):
+            continue        
+        names.append(filename)
+    
+    id2nrs = {}
+    with open(used_path, "r") as f:
+        for line in f.readlines():
+            spl = line.split(";")
+            id = spl[1]
+            nr = spl[0]
+            if id not in id2nrs:
+                id2nrs[id] = []
+            id2nrs[id].append(nr)
+        
+    types = {}
+    with open(mapping_path, "r") as f:
+        for line in f.readlines():
+            spl = line.strip().split(";")
+            id = spl[2]         
+            if id in id2nrs.keys():
+                for nr in id2nrs[id]:
+                    types[nr] = spl[0].decode("utf-8")
+        
+    return types
+        
+def print_stats(db, ann, types):
+    
+    gms = {}
+    gmgs = {}
+    wbalance = {}
+    tbalance = {}    
+    nibalance = {}
+    
+    for filename, file in db.file_index.iteritems():
+        
+        type = types[filename]
+        idx = 0
+        
+        if ann == "rejected":
+            if db.rejected(file):
+                path = os.path.join(wc.new_path(), filename)
+                wcnt, ms, mgs, nidcnt = count_file_stats(path)
+                                    
+                merge_dicts(wbalance, {type : wcnt})
+                merge_dicts(tbalance, {type : 1})
+                merge_dicts(nibalance, {type : nidcnt})                 
+                
+                if type not in gms:
+                    gms[type] = {}
+                if type not in gmgs:
+                    gmgs[type] = {}
+                    
+                merge_dicts(gms[type], ms)
+                merge_dicts(gmgs[type], mgs)
+                
+        else: 
+            if db.rejected(file):
+                continue        
+                        
+            for annotation in file.findall(ann):            
+                if db.finished(annotation):
+                    if ann == "ann":                
+                        path = wc.upload_path(filename, idx)
+                    else:
+                        path = wc.upload_prim_path(filename)
+                        
+                    wcnt, ms, mgs, nidcnt = count_file_stats(path)
+                                    
+                    merge_dicts(wbalance, {type : wcnt})
+                    merge_dicts(tbalance, {type : 1}) 
+                    merge_dicts(nibalance, {type : nidcnt})           
+                         
+                    if type not in gms:
+                        gms[type] = {}
+                    if type not in gmgs:
+                        gmgs[type] = {}
+                        
+                    merge_dicts(gms[type], ms)
+                    merge_dicts(gmgs[type], mgs)
+                                
+                idx = idx + 1
+    
+    
+    gwcnt = reduce((lambda l, a : l + a), wbalance.values(), 0)
+    tc = reduce((lambda l, a : l + a), tbalance.values(), 0)
+    print
+    print "######## Statystyki ilosciowe"
+    print fill_custom("Typ tekstow", 60, " "), fill("Liczba tekstow"), fill("Liczba slow"), fill("Procent slow")
+    print fill_custom("", 120, "-")
+    for type in sorted(set(wbalance.keys()) | set(tbalance.keys())):
+        percent = round(1.0 * wbalance[type] / gwcnt * 100, 2)
+        print fill_custom(type.encode("utf-8"), 60, " "), fill(str(tbalance[type])), fill(str(wbalance[type])), fill(str(percent) + "%")        
+    print fill_custom("", 120, "-")
+    print fill_custom("dowolny", 60, " "), fill(str(tc)), fill(str(gwcnt)), fill("100.0%")    
+    
+    if ann != "rejected":
+        print
+        print "######## Statystyki wystapien"
+        print fill_custom("Typ tekstow", 60, " "), fill("Wystapien/tekst"), fill("Dl. wystapienia")
+        print fill_custom("", 100, "-")
+        tmc = 0
+        tsc = 0
+        for type in sorted(set(gms.keys())):            
+            mention_count = reduce((lambda l, a : l + a), gms[type].values(), 0)
+            segment_count = reduce((lambda a, (k, v) : k * v + a), gms[type].iteritems(), 0)       
+            avg_ms = round(1.0 * segment_count / mention_count, 2)
+            avg_mpt = round(1.0 * mention_count / tbalance[type], 2)
+            print fill_custom(type.encode("utf-8"), 60, " "), fill(str(avg_mpt)), fill(str(avg_ms))
+            tmc = tmc + mention_count
+            tsc = tsc + segment_count        
+        print fill_custom("", 100, "-")
+        
+        if tmc == 0:
+            tavg_ms = 0
+        else:
+            tavg_ms = round(1.0 * tsc / tmc, 2)
+            
+        if tc == 0:
+            tavg_mpt = 0
+        else:
+            tavg_mpt = round(1.0 * tmc / tc, 2)
+            
+        print fill_custom("dowolny", 60, " "), fill(str(tavg_mpt)), fill(str(tavg_ms))
+        
+        print
+        print "######## Statystyki klastrow i linkow"
+        print fill_custom("Typ tekstow", 60, " "), fill("Klastrow/tekst"), fill("Wielkosc klastra"), fill("Linkow/tekst")
+        print fill_custom("", 120, "-")
+        tmc = 0
+        tsc = 0
+        tnic = 0
+        for type in sorted(set(gmgs.keys())):            
+            group_count = reduce((lambda l, a : l + a), gmgs[type].values(), 0)
+            mention_count = reduce((lambda a, (k, v) : k * v + a), gmgs[type].iteritems(), 0)       
+            avg_ms = round(1.0 * mention_count / group_count, 2)
+            avg_mpt = round(1.0 * group_count / tbalance[type], 2)
+            avg_ni = round(1.0 * nibalance[type] / tbalance[type], 2)
+            print fill_custom(type.encode("utf-8"), 60, " "), fill(str(avg_mpt)), fill(str(avg_ms)), fill(str(avg_ni))
+            tmc = tmc + group_count
+            tsc = tsc + mention_count
+            tnic = tnic + nibalance[type]  
+        print fill_custom("", 120, "-")
+        
+        if tmc == 0:
+            tavg_ms = 0
+        else:
+            tavg_ms = round(1.0 * tsc / tmc, 2)
+            
+        if tc == 0:
+            tavg_mpt = 0
+            tavg_ni = 0
+        else:
+            tavg_mpt = round(1.0 * tmc / tc, 2)
+            tavg_ni = round(1.0 * tnic / tc, 2)
+            
+        print fill_custom("dowolny", 60, " "), fill(str(tavg_mpt)), fill(str(tavg_ms)), fill(str(tavg_ni))
+
+
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog CONFIG USED_LIST MAPPING""")
+    (options, args) = optparser.parse_args()
+    if len(args) < 3:
+        optparser.print_help()
+        sys.exit(0)
+        
+    conf_path = args[0]
+    used_path = args[1]
+    mapping_path = args[2]
+    cfg = Config(conf_path)
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), int(cfg["anno_per_file"]))    
+    
+    types = get_text_types(db, used_path, mapping_path)
+    
+    print "################ Statystyki tekstow anotowanych #############################"
+    print_stats(db, "ann", types)
+    print
+    print "################ Statystyki tekstow superanotowanych ########################"
+    print_stats(db, "s_ann", types)
+    print
+#    print "################ Statystyki tekstow odrzuconych #############################"
+#    print_stats(db, "rejected", types) 
diff --git a/scripts/db_detailed_stats.py b/scripts/db_detailed_stats.py
new file mode 100755
index 0000000..33f826e
--- /dev/null
+++ b/scripts/db_detailed_stats.py
@@ -0,0 +1,262 @@
+# -*- coding: utf-8 -*-
+
+#!/usr/bin/env python
+import re
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+
+def get_desired():
+    result = {}
+    result[u"Dzienniki"] = 25.5
+    result[u"Pozostałe periodyki"] = 23.5
+    result[u"Książki publicystyczne"] = 1.0
+    result[u"Literatura piękna"] = 16.0
+    result[u"Literatura faktu"] = 5.5
+    result[u"Typ informacyjno-poradnikowy"] = 5.5
+    result[u"Typ naukowo-dydaktyczny"] = 2.0
+    result[u"Internetowe interaktywne (blogi, fora, usenet)"] = 3.5
+    result[u"Internetowe nieinteraktywne (statyczne strony, Wikipedia)"] = 3.5
+    result[u"Quasi-mówione (protokoły sesji parlamentu)"] = 2.5
+    result[u"Mówione medialne"] = 2.5
+    result[u"Mówione konwersacyjne"] = 5.0
+    result[u"Inne teksty pisane"] = 3.0
+    result[u"Książka niebeletrystyczna nieklasyfikowana"] = 1.0
+    
+    return result
+
+def fill(text):
+    return fill_custom(text, 20, " ")
+
+def fill_custom(text, l, sym):
+    text = text.decode("utf-8")
+    to_add = max(0, l - len(text))
+    spaces = ""
+    for i in range(to_add):
+        spaces = spaces + sym
+    return (text + spaces).encode("utf-8")
+
+def get_span_size(span, words):    
+    w = span.split(",")
+    size = 0
+    for fragment in w:
+        f = fragment.split("..")
+        id1 = words.index(f[0])
+        id2 = words.index(f[-1])        
+        size = size + id2 - id1 + 1
+                
+    return size
+        
+def count_file_stats(path):
+    words = []
+    id = re.compile("<word.* id=\"(.*?)\".*")
+    with open(path + "_words.xml", "r") as f:
+        for line in f.readlines():
+            groups = id.findall(line)
+            if len(groups) == 1:                
+                ident = groups[0]
+                words.append(ident)                
+        
+    sp = re.compile("<markable.*span=\"(.*?)\".*")
+    mg = re.compile(".*mention_group=\"(.*?)\".*")
+    ni = re.compile(".*near_identity=\"(.*?)\".*") 
+    
+    sets = {}
+    near_id = 0
+    mentions = 0
+    mention_sizes = {}
+    with open(path + "_mentions.xml", "r") as f:
+        for line in f.readlines():
+            groups = sp.findall(line)          
+            if len(groups) == 1:
+                mentions = mentions + 1                
+                span = groups[0]
+                             
+                mention_size = get_span_size(span, words)
+                if mention_size in mention_sizes:
+                    mention_sizes[mention_size] = mention_sizes[mention_size] + 1
+                else: 
+                    mention_sizes[mention_size] = 1 
+                
+                group = mg.findall(line)[0]                
+                near = ni.findall(line)[0]
+                
+                if near != "empty" and near != "":
+                    near_id = near_id + 1
+                    
+                if group != "empty" and group != "":                    
+                    if group in sets:
+                        sets[group] = sets[group] + 1
+                    else:       
+                        sets[group] = 1
+    
+    mg_sizes = {}
+    for key, val in sets.iteritems():
+        if val in mg_sizes:
+            mg_sizes[val] = mg_sizes[val] + 1
+        else:
+            mg_sizes[val] = 1
+            
+    return len(words), mention_sizes, mg_sizes, near_id
+
+def merge_dicts(dict1, dict2):
+    
+    for key, val in dict2.iteritems():
+        if key in dict1:
+            dict1[key] = dict1[key] + val
+        else:
+            dict1[key] = val
+
+def get_text_types(db, used_path, mapping_path):    
+    names = []
+    for filename, file in db.file_index.iteritems():
+        if db.rejected(file):
+            continue        
+        names.append(filename)
+    
+    id2nrs = {}
+    with open(used_path, "r") as f:
+        for line in f.readlines():
+            spl = line.split(";")
+            id = spl[1]
+            nr = spl[0]
+            if id not in id2nrs:
+                id2nrs[id] = []
+            id2nrs[id].append(nr)
+        
+    types = {}
+    with open(mapping_path, "r") as f:
+        for line in f.readlines():
+            spl = line.strip().split(";")
+            id = spl[2]         
+            if id in id2nrs.keys():
+                for nr in id2nrs[id]:
+                    types[nr] = spl[0].decode("utf-8")
+        
+    return types
+        
+def print_stats(db, ann, types):    
+    gnidcnt = 0
+    gwcnt = 0
+    gms = {}
+    gmgs = {}
+    wbalance = {}
+    tbalance = {}
+    
+    tc = 0
+    
+    for filename, file in db.file_index.iteritems():
+        
+        type = types[filename]
+        idx = 0
+        
+        if ann == "rejected":
+            if db.rejected(file):
+                path = os.path.join(wc.new_path(), filename)
+                wcnt, ms, mgs, nidcnt = count_file_stats(path)
+                                    
+                merge_dicts(wbalance, {type : wcnt})
+                merge_dicts(tbalance, {type : 1}) 
+                    
+                gwcnt = gwcnt + wcnt
+                gnidcnt = gnidcnt + nidcnt                
+                merge_dicts(gms, ms)
+                merge_dicts(gmgs, mgs)
+                
+                tc = tc + 1
+                
+        else: 
+            if db.rejected(file):
+                continue        
+                        
+            for annotation in file.findall(ann):            
+                if db.finished(annotation):
+                    if ann == "ann":                
+                        path = wc.upload_path(filename, idx)
+                    else:
+                        path = wc.upload_prim_path(filename)
+                        
+                    wcnt, ms, mgs, nidcnt = count_file_stats(path)
+                                    
+                    merge_dicts(wbalance, {type : wcnt})
+                    merge_dicts(tbalance, {type : 1}) 
+                    
+                    gwcnt = gwcnt + wcnt
+                    gnidcnt = gnidcnt + nidcnt                
+                    merge_dicts(gms, ms)
+                    merge_dicts(gmgs, mgs)
+                    
+                    tc = tc + 1
+                                
+                idx = idx + 1
+        
+    desired = get_desired()
+    print
+    print fill_custom("Typ tekstow", 60, " "), fill("Liczba tekstow"), fill("Liczba slow"), fill("Procent slow"), fill("Docelowy procent")
+    print fill_custom("", 150, "-")
+    for type in sorted(set(wbalance.keys()) | set(tbalance.keys())):
+        percent = round(1.0 * wbalance[type] / gwcnt * 100, 2)
+        print fill_custom(type.encode("utf-8"), 60, " "), fill(str(tbalance[type])), fill(str(wbalance[type])), fill(str(percent) + "%"), fill(str(desired[type])+"%")        
+    print fill_custom("", 150, "-")
+    print fill_custom("dowolny", 60, " "), fill(str(tc)), fill(str(gwcnt)), fill("100.0%"), fill("100.0%")    
+    
+    print
+    
+    if ann != "rejected":        
+        print "Wielkosci wystapien:"
+        suma = 0
+        for key, val in sorted(gms.iteritems()):
+            print "  ", val, "wystapien o wielkosci", key
+            suma = suma + val
+        print "  ", "--------------------------"
+        print "  ", suma, "wystapien o dowolnej wielkosci"
+        
+        print 
+        
+        print "Wielkosci klastrow:"
+        suma = 0
+        for key, val in sorted(gmgs.iteritems()):
+            print "  ", val, "klastrow o wielkosci", key
+            suma = suma + val
+        print "  ", "--------------------------"
+        print "  ", suma, "klastrow o dowolnej wielkosci"
+
+        print
+        
+        print "Liczba linkow:", gnidcnt
+
+
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog CONFIG USED_LIST MAPPING""")
+    (options, args) = optparser.parse_args()
+    if len(args) < 3:
+        optparser.print_help()
+        sys.exit(0)
+        
+    conf_path = args[0]
+    used_path = args[1]
+    mapping_path = args[2]
+    cfg = Config(conf_path)
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), int(cfg["anno_per_file"]))    
+    
+    types = get_text_types(db, used_path, mapping_path)
+    
+    print "################ Statystyki anotacji #########################"
+    print_stats(db, "ann", types)
+    print
+    print "################ Statystyki superanotacji ####################"
+    print_stats(db, "s_ann", types)
+    print
+#    print "################ rejected files stats####################"
+#    print_stats(db, "rejected", types) 
diff --git a/scripts/db_stats.py b/scripts/db_stats.py
new file mode 100644
index 0000000..b0e473f
--- /dev/null
+++ b/scripts/db_stats.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+
+def fill(text):
+    return fill_custom(text, 20, " ")
+
+def fill_custom(text, l, sym):
+    to_add = max(0, l - len(text))
+    spaces = ""
+    for i in range(to_add):
+        spaces = spaces + sym
+    return text + spaces
+
+def count_rejected(db):
+    rejected = 0
+    for filename, file in db.file_index.iteritems():
+       if db.rejected(file):
+           rejected = rejected + 1
+    return rejected
+        
+def count_anno_stats(db, all, anno_per_file):
+    finished = {}
+    current = {}
+    returned = {}
+    returned_fixed = {}
+    finished_by_all = 0
+    fetched_by_all = 0
+    
+    for filename, file in db.file_index.iteritems():
+        if db.rejected(file):
+            continue
+        
+        fin = True
+        for annotation in file.findall("ann"):
+            owner = annotation.find("annName").text            
+                
+            if not owner in finished:
+                finished[owner] = 0
+            if not owner in current:
+                current[owner] = 0                 
+            if not owner in returned:
+                returned[owner] = 0   
+            if not owner in returned_fixed:
+                returned_fixed[owner] = 0     
+            
+            if db.owned(annotation):                
+                current[owner] = current[owner] + 1
+            elif db.returned(annotation):
+                returned[owner] = returned[owner] + 1
+                if db.fixed(annotation):
+                    returned_fixed[owner] = returned_fixed[owner] + 1                
+            elif db.finished(annotation):
+                finished[owner] = finished[owner] + 1
+            else:
+                print "Strange state! ", annotation.text
+                
+            if not db.finished(annotation):
+                fin = False
+        
+        if len(file.findall("ann")) == anno_per_file:
+            if fin:
+                finished_by_all = finished_by_all + 1
+            else:
+                fetched_by_all = fetched_by_all + 1  
+        
+    
+    print fill("Login"), fill("Akt. pobr."), fill("Zwrocone (Napr.)"), fill("Zakonczone"), fill("Do pobrania")
+    print fill_custom("", 100, "-")
+    for user in set(current.keys()) | set(finished.keys()) | set(returned_fixed.keys()) | set(returned.keys()):
+        print fill(user), fill(str(current[user])), fill(str(returned[user]) + " (" + str(returned_fixed[user]) + ")"), fill(str(finished[user])),
+        priority, normal = db.for_annotation(user)
+        print fill(str(len(normal)))
+                
+    print fill_custom("", 100, "-")
+    all_curr = sum([i for i in current.values()])
+    all_ret = sum([i for i in returned.values()])
+    all_ret_fixed = sum([i for i in returned_fixed.values()])    
+    all_fin = sum([i for i in finished.values()])
+    print fill("Suma"), fill(str(all_curr)), fill(str(all_ret) + " (" + str(all_ret_fixed) + ")"), fill(str(all_fin)), fill(str(all - all_curr - all_fin - all_ret))
+    print ""
+    print "Pobranych (nie zakonczonych) jednoczesnie przez", anno_per_file, "tekstow:", fetched_by_all
+    print "Zakonczonych jednoczesnie przez", anno_per_file, "tekstow:", finished_by_all
+    print "Pozostalo do pobrania naprawionych:", all_ret_fixed
+    print "Pozostalo do obejrzenia zwroconych:", all_ret - all_ret_fixed
+    print 
+    
+    return finished_by_all   
+           
+           
+def count_super_stats(db, all, anno_per_file):
+    finished = {}
+    current = {}
+    returned = {}
+    returned_fixed = {}
+    
+    for filename, file in db.file_index.iteritems():
+        if db.rejected(file):
+            continue
+        
+        for annotation in file.findall("s_ann"):
+            owner = annotation.find("annName").text            
+                
+            if not owner in finished:
+                finished[owner] = 0
+            if not owner in current:
+                current[owner] = 0                 
+            if not owner in returned:
+                returned[owner] = 0   
+            if not owner in returned_fixed:
+                returned_fixed[owner] = 0     
+            
+            if db.owned(annotation):                
+                current[owner] = current[owner] + 1
+            elif db.returned(annotation):
+                returned[owner] = returned[owner] + 1
+                if db.fixed(annotation):
+                    returned_fixed[owner] = returned_fixed[owner] + 1                
+            elif db.finished(annotation):
+                finished[owner] = finished[owner] + 1
+            else:
+                print "Strange state! ", annotation.text
+    
+    print fill("Login"), fill("Akt. pobr."), fill("Zwrocone (Napr.)"), fill("Zakonczone"), fill("Do pobrania")
+    print fill_custom("", 100, "-")
+    for user in set(current.keys()) | set(finished.keys()) | set(returned_fixed.keys()) | set(returned.keys()):
+        print fill(user), fill(str(current[user])), fill(str(returned[user]) + " (" + str(returned_fixed[user]) + ")"), fill(str(finished[user])),
+        priority, normal = db.for_adjudication(user)
+        print fill(str(len(normal)))
+                
+    print fill_custom("", 100, "-")
+    all_curr = sum([i for i in current.values()])
+    all_ret = sum([i for i in returned.values()])
+    all_ret_fixed = sum([i for i in returned_fixed.values()])    
+    all_fin = sum([i for i in finished.values()])
+    print fill("Suma"), fill(str(all_curr)), fill(str(all_ret) + " (" + str(all_ret_fixed) + ")"), fill(str(all_fin)), fill(str(all - all_curr - all_fin - all_ret))
+    print ""
+    print "Pozostalo do pobrania naprawionych:", all_ret_fixed
+    print "Pozostalo do obejrzenia zwroconych:", all_ret - all_ret_fixed
+    print 
+    
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog CONFIG""")
+    (options, args) = optparser.parse_args()
+    if len(args) < 1:
+        optparser.print_help()
+        sys.exit(0)
+        
+    conf_path = args[0]
+    cfg = Config(conf_path)
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), int(cfg["anno_per_file"]))    
+    anno_per_file = int(cfg["anno_per_file"])
+
+    rejected = count_rejected(db)  
+    print    
+    print fill_custom("", 100, "-")
+    all = len(db.file_index.keys())
+    print "Wszystkich tekstow w bazie: " + str(all)    
+    print "Odrzuconych: " + str(rejected)
+    print "Wszystkich tekstow w bazie bez odrzuconych: " + str(all - rejected)    
+    all = all * anno_per_file - rejected
+    print "Anotacji do wykonania: " + str(all)
+        
+    print fill_custom("", 100, "-")
+    print 
+    
+    print fill_custom("----Anotacja", 100, "-")
+    anno_count = count_anno_stats(db, all, anno_per_file)
+        
+    print fill_custom("----SuperAnotacja", 100, "-")
+    count_super_stats(db, anno_count, anno_per_file)
+            
+    
diff --git a/scripts/extract_files.py b/scripts/extract_files.py
new file mode 100755
index 0000000..4d59d64
--- /dev/null
+++ b/scripts/extract_files.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+
+def extract_file(file, exts, target_dir):
+    print "Extracting file", file
+    for ext in exts:
+        filename = file + ext
+        print "\t-", os.path.basename(filename) 
+        shutil.copy(filename, target_dir)    
+
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog [options] CONFIG TYPE TARGET_DIR""")
+    optparser.add_option("--extensions", dest="exts", default=".mmax,_mentions.xml,_words.xml",
+            help="List of comma-separated file extensions")
+    (options, args) = optparser.parse_args()
+    if len(args) != 3:
+        optparser.print_help()
+        sys.exit(0)
+
+    conf_path = args[0]
+    cfg = Config(conf_path)
+    type = args[1]
+    target_dir = args[2]
+    anno_per_file = int(cfg["anno_per_file"])
+    exts = options.exts.split(",")
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), anno_per_file)
+    
+    poss_types = ["finished", "returned", "annotated"]
+    if type not in poss_types:
+        print "Possible types: ", poss_types
+        sys.exit(0)
+    
+    filepaths = {}
+    for filename, file_elem in db.file_index.iteritems():
+        if db.rejected(file_elem):
+            continue
+        
+        if type == "finished":
+            sann = file_elem.find("s_ann")
+            if sann is not None and db.finished(sann):                
+                filepaths[wc.upload_prim_path(filename)] = None
+                
+        elif type == "annotated":
+            idx = 0
+            for ann in file_elem.findall("ann"):
+                if db.finished(ann):
+                    filepaths[wc.upload_path(filename, idx)] = None
+                idx = idx + 1
+                
+        elif type == "returned":
+            reason = None
+            idx = 0
+            for ann in file_elem.findall("ann"):                
+                if db.returned(ann):
+                    reason = db.get_reason(ann)
+                    break
+                idx = idx + 1
+                
+            if reason is not None:
+                filepaths[wc.upload_path(filename, idx)] = reason
+                continue
+            
+            reason = None  
+            for sann in file_elem.findall("s_ann"):
+                if db.returned(sann):
+                    reason = db.get_reason(sann)
+                    break
+                
+            if reason is not None:
+                filepaths[wc.upload_prim_path(filename)] = reason
+        
+    for k, v in filepaths.iteritems():
+        extract_file(k, exts, target_dir)
+    
+    if type == "returned":    
+        filename = os.path.join(target_dir, "reasons.txt")
+        with open(filename, 'w') as f:
+            for k, v in sorted(filepaths.iteritems()):
+                name = os.path.basename(k)
+                f.write(name)
+                f.write(" - ")
+                f.write(v.encode("utf-8"))
+                f.write("\n")  
+                                      
\ No newline at end of file
diff --git a/scripts/fill_empty_repo.sh b/scripts/fill_empty_repo.sh
new file mode 100755
index 0000000..921a637
--- /dev/null
+++ b/scripts/fill_empty_repo.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+if [ $# == 2 ]; then
+	echo "Filling empty repository in: $1 with $2 annos per file"
+else
+	echo "Usage: $0 DIR ANNO_PER_FILE"
+	exit
+fi
+
+path=$1
+annos=$2
+
+mkdir $path/new
+mkdir $path/annotation
+
+c=0
+for x in {A..Z}
+do
+	mkdir $path/annotation/$x
+	c=$((c+1))
+	if [ $c -ge $annos ]
+	then
+		break
+	fi 
+done 
+
+mkdir $path/adjudication
+
+echo "<files></files>" > $path/db.xml
diff --git a/scripts/fix_files.py b/scripts/fix_files.py
new file mode 100755
index 0000000..82a5001
--- /dev/null
+++ b/scripts/fix_files.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+            
+def fix_file(wc, db, file_id, contents, anno_per_file):
+    file_elem = db.file_index[file_id]
+    if file_elem is None:    
+        print "File", file_id, "not found in database." 
+        return False
+    
+    ann_elem = None
+    idx = -1
+    for ann in file_elem.findall("ann") + file_elem.findall("s_ann"):
+        idx = idx + 1
+        if db.returned(ann):
+            ann_elem = ann
+            break
+                
+    if ann_elem is None:
+        print "File", file_id, "not returned in database." 
+        return False
+    
+    print "Fixing file " + file_id
+                
+    try:
+        db.fix(ann_elem)
+        
+        if ann_elem.tag == "ann":            
+            wc.upload(file_id, idx, contents)
+        elif ann_elem.tag == "s_ann":
+            wc.upload_prim(file_id, contents)
+                      
+    except Exception as ex:
+        print "\t error: " + str(ex)
+        wc.revert()
+        return False
+    
+    return True          
+                        
+def match_ext(path, exts):
+    for ext in exts:
+        if path.endswith(ext):
+            return ext
+
+def path_id(path, ext):
+    _, filename = os.path.split(path)
+    return re.sub("%s$" % ext, "", filename)
+
+def group_paths(paths, exts):
+    result = defaultdict(list)            
+    for path in paths:
+        ext = match_ext(path, exts)
+        if (ext != None):
+            file_id = path_id(path, ext)            
+            if file_id not in result:
+                result[file_id] = {}            
+            with open(path, "r") as f:
+                result[file_id][ext]=f.read()
+    return result
+
+def get_rec_paths(paths):
+    result = []
+    for path in paths:
+        if os.path.isdir(path):
+            for dirname, dirnames, filenames in os.walk(path):                
+                for filename in filenames:
+                    result.append(os.path.join(dirname, filename))                
+        else:
+            result.append(path)
+    return result
+
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog [options] CONFIG FILES""")
+    optparser.add_option("--extensions", dest="exts", default=".mmax,_mentions.xml,_words.xml",
+            help="List of comma-separated file extensions")
+    (options, args) = optparser.parse_args()
+    if len(args) < 2:
+        optparser.print_help()
+        sys.exit(0)
+
+    conf_path = args[0]    
+    cfg = Config(conf_path)
+    anno_per_file = int(cfg["anno_per_file"])
+    paths = get_rec_paths(args[1:])
+    exts = options.exts.split(",")
+    files = group_paths(paths, exts)
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), anno_per_file)
+    
+    success = []
+    fail = []
+    for file_id, contents in files.iteritems():
+        if fix_file(wc, db, file_id, contents, anno_per_file):
+            success.append(file_id)
+        else:
+            fail.append(file_id)     
+    
+    db.save()           
+    wc.commit("Fixed files: " + str(success))        
+    
+    print ""
+    if len(success) > 0:
+        print "Fixed files: " + str(success)
+    if len(fail) > 0:
+        print "Failed to fix files: " + str(fail)
+        
diff --git a/scripts/quasi.py b/scripts/quasi.py
new file mode 100755
index 0000000..c042e11
--- /dev/null
+++ b/scripts/quasi.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+import re
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+
+def fill(text):
+    return fill_custom(text, 20, " ")
+
+def fill_custom(text, l, sym):
+    to_add = max(0, l - len(text))
+    spaces = ""
+    for i in range(to_add):
+        spaces = spaces + sym
+    return text + spaces
+
+def parse_span(span, words_list):    
+    words = []
+    
+    w = span.split(",")    
+    for fragment in w:
+        f = fragment.split("..")
+        id1 = words_list.index(f[0])
+        id2 = words_list.index(f[-1])        
+        for i in range(id1, id2+1):
+            words.append(words_list[i])
+                
+    return words
+
+def get_context(ids, words_list, size):
+    first = -1
+    last = -1
+    
+    i = 0
+    for i in range(len(words_list)):
+        w = words_list[i]
+        if w in ids:
+            if first == -1:
+                first = i
+            last = i
+
+    first = max(0, first - size)
+    last = min(len(words_list), last + size)
+
+    return words_list[first:last]
+        
+def print_file(path):                
+    word_id_2_orth = {}
+    words_list = [] 
+    w = re.compile("<word.* id=\"(.*?)\".*>(.*?)</word>.*")
+    with open(path + "_words.xml", "r") as f:
+        for line in f.readlines():
+            groups = w.findall(line)
+            if len(groups) == 1:
+                group = groups[0]
+                id = group[0]
+                orth = group[1]
+                word_id_2_orth[id] = orth
+                words_list.append(id)
+    
+    me = re.compile("<markable.*id=\"(.*?)\".*")
+    sp = re.compile(".*span=\"(.*?)\".*")
+    co = re.compile(".*comment=\"(.*?)\".*")
+    ni = re.compile(".*near_identity=\"(.*?)\".*") 
+ 
+    mention_id_2_span = {}
+    mention_id_2_comment = {}          
+    near_links = []
+    with open(path + "_mentions.xml", "r") as f:
+        for line in f.readlines():
+            groups1 = me.findall(line)
+            groups2 = sp.findall(line)
+            groups3 = co.findall(line)       
+            
+            if len(groups1) == 1 and len(groups2) == 1:            
+                id = groups1[0]
+                span = groups2[0]
+                mention_id_2_span[id] = parse_span(span, words_list)
+                
+                if len(groups3) == 1:
+                    mention_id_2_comment[id] = groups3[0]                
+                                 
+                near = ni.findall(line)
+                if len(near) == 1 and near[0] != "empty" and near[0] != "":
+                    near_links.append((id, near[0]))                                        
+    
+    if len(near_links) > 0:        
+        print "###", "Tekst", path.split("/")[-1], "###"
+        print 
+        
+    c = 0
+    for m1, m2 in near_links:
+        comments = []
+        if m1 in mention_id_2_comment:
+            comments.append(mention_id_2_comment[m1])
+        if m2 in mention_id_2_comment:
+            comments.append(mention_id_2_comment[m2])
+        
+        if m1 not in mention_id_2_span or m2 not in mention_id_2_span:
+            print "ERROR", m1, m2
+            continue
+        
+        span1 = mention_id_2_span[m1]
+        span2 = mention_id_2_span[m2]
+        
+        spans = set(span1)
+        spans = spans.union(set(span2))    
+        ctx = get_context(spans, words_list, 3)
+        
+        result = ""        
+        for wid in ctx:
+            result = result + " "
+            if wid == span1[0]:
+                result = result + "["
+            if wid == span2[0]:
+                result = result + "["
+            
+            result = result + word_id_2_orth[wid]    
+            
+            if wid == span1[-1]:
+                result = result + "]"
+            if wid == span2[-1]:
+                result = result + "]"                            
+        
+        m1orth = "[" + reduce(lambda a, i: a + " " + i, map(lambda i : word_id_2_orth[i], span1)) + "]"
+        m2orth = "[" + reduce(lambda a, i: a + " " + i, map(lambda i : word_id_2_orth[i], span2)) + "]"
+        
+        if words_list.index(span1[0]) <= words_list.index(span2[0]):
+            print str(c)+".", m1orth, "<-->", m2orth
+        else:
+            print str(c)+".", m2orth, "<-->", m1orth
+            
+        for comm in comments:            
+            print "Komentarz:", comm
+        print "...", result, "..."
+        print
+             
+        c = c + 1
+        
+    return c
+        
+def print_quasi(db):
+    links = 0        
+    for filename, file in db.file_index.iteritems():
+        
+        if db.rejected(file):
+            continue        
+                        
+        sann = file.find("s_ann")
+        
+        if sann is not None and db.finished(sann):            
+            path = wc.upload_prim_path(filename)              
+            links = links + print_file(path)
+            
+    return links
+
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog CONFIG """)
+    (options, args) = optparser.parse_args()
+    if len(args) < 1:
+        optparser.print_help()
+        sys.exit(0)
+        
+    conf_path = args[0]
+    cfg = Config(conf_path)
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), int(cfg["anno_per_file"]))    
+    
+    l = print_quasi(db)
+    print "##################"
+    print
+    print "Wszystkich linkow:", l
+    
diff --git a/scripts/reject_files.py b/scripts/reject_files.py
new file mode 100755
index 0000000..d599392
--- /dev/null
+++ b/scripts/reject_files.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+
+def get_reason(db, file_id):
+    reason = ""
+    file_elem = db.file_index[file_id]
+    for ann in file_elem.findall("ann"):
+        if db.returned(ann):
+            reason = db.get_reason(ann)
+            break      
+    
+    for ann in file_elem.findall("s_ann"):
+        if db.returned(ann):
+            reason = db.get_reason(ann)
+            break
+        
+    return reason
+    
+
+def reject_file(db, file_id, reason):
+    try:
+        if reason == "":
+            reason = get_reason(db, file_id)
+
+        print "Rejecting", file_id, "because", reason     
+        db.reject(file_id, reason)
+    except Exception as ex:
+        print file_id, "-", str(ex)
+        return False
+    
+    return True
+
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog -r REASON CONFIG FILES""")
+    optparser.add_option("-r", dest="reason", default="", help="Reason to reject files")
+    (options, args) = optparser.parse_args()
+    if len(args) < 2:
+        optparser.print_help()
+        sys.exit(0)
+    
+    conf_path = args[0]
+    cfg = Config(conf_path)
+    ids = args[1:]
+    reason = options.reason
+    anno_per_file = int(cfg["anno_per_file"])
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), anno_per_file)
+    
+    success = []
+    fail = []
+    for file_id in ids:
+        if reject_file(db, file_id, reason):
+            success.append(file_id)
+        else:
+            fail.append(file_id)       
+
+    db.save()
+    wc.commit("Rejected files: "+str(ids))
+            
+    print ""
+    if len(success) > 0:
+        print "Rejected files: "+str(success)
+    if len(fail) > 0:
+        print "Failed to reject files: "+str(fail)
+        
\ No newline at end of file
diff --git a/scripts/remove_files.py b/scripts/remove_files.py
new file mode 100755
index 0000000..4075481
--- /dev/null
+++ b/scripts/remove_files.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+
+def remove_file(wc, db, file_id, extensions, anno_per_file):
+    try:
+        db.remove(file_id)
+    except Exception as ex:
+        print str(ex)
+        return False
+    
+    print "Removing "+file_id+" :"
+    for ext in extensions:
+        file = file_id+ext     
+        for r in wc.remove(file, anno_per_file):
+            print "\t - "+r
+    
+    return True
+
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog [options] CONFIG FILES""")
+    optparser.add_option("--extensions", dest="exts", default=".mmax,_mentions.xml,_words.xml",
+            help="List of comma-separated file extensions")
+    (options, args) = optparser.parse_args()
+    if len(args) < 2:
+        optparser.print_help()
+        sys.exit(0)
+
+    conf_path = args[0]
+    cfg = Config(conf_path)
+    ids = args[1:]
+    extensions = options.exts.split(",")
+    anno_per_file = int(cfg["anno_per_file"])
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), anno_per_file)
+    
+    success = []
+    fail = []
+    for file_id in ids:
+        if remove_file(wc, db, file_id, extensions, anno_per_file):
+            success.append(file_id)
+        else:
+            fail.append(file_id)       
+
+    db.save()
+    wc.commit("Removed files: "+str(ids))
+            
+    print ""
+    if len(success) > 0:
+        print "Removed files: "+str(success)
+    if len(fail) > 0:
+        print "Failed to remove files: "+str(fail)
\ No newline at end of file
diff --git a/scripts/stats_atlas.py b/scripts/stats_atlas.py
new file mode 100755
index 0000000..6a5a54a
--- /dev/null
+++ b/scripts/stats_atlas.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import re
+import shutil
+from collections import defaultdict
+from optparse import OptionParser
+
+# Solution with no hard coded path would be welcome
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), ".."))
+
+from dfs.database import Database
+from dfs.repo import Repo
+from dfs.config import Config
+
+def fill(text):
+    return fill_custom(text, 20, " ")
+
+def fill_custom(text, l, sym):
+    to_add = max(0, l - len(text))
+    spaces = ""
+    for i in range(to_add):
+        spaces = spaces + sym
+    return text + spaces
+
+def count_rejected(db):
+    rejected = 0
+    for filename, file in db.file_index.iteritems():
+       if db.rejected(file):
+           rejected = rejected + 1
+    return rejected
+        
+
+def count_file_stats(path):
+    text = ""
+    with open(path + ".txt", "r") as f:
+        for line in f.readlines():
+            if line.startswith("#### SUMMARIES ####"):
+                break;
+            text += line +"\n"                
+                    
+    return len(text)
+        
+def count_anno_stats(db, all, anno_per_file):
+    finished = {}
+    current = {}
+    returned = {}
+    returned_fixed = {}
+    finished_by_all = 0
+    fetched_by_all = 0
+    chars = {}
+    
+    for filename, file in db.file_index.iteritems():
+        if db.rejected(file):
+            continue
+        
+        fin = True
+        idx = 0
+        for annotation in file.findall("ann"):
+            owner = annotation.find("annName").text            
+                
+            if not owner in finished:
+                finished[owner] = 0
+            if not owner in current:
+                current[owner] = 0                 
+            if not owner in returned:
+                returned[owner] = 0   
+            if not owner in returned_fixed:
+                returned_fixed[owner] = 0     
+            
+            if db.owned(annotation):                
+                current[owner] = current[owner] + 1
+            elif db.returned(annotation):
+                returned[owner] = returned[owner] + 1
+                if db.fixed(annotation):
+                    returned_fixed[owner] = returned_fixed[owner] + 1                
+            elif db.finished(annotation):
+                finished[owner] = finished[owner] + 1
+            else:
+                print "Strange state! ", annotation.text
+                
+            if not db.finished(annotation):
+                fin = False
+            else:
+                path = wc.upload_path(filename, idx)
+                ccnt = count_file_stats(path)
+                if owner not in chars:
+                    chars[owner] = 0
+                chars[owner] = chars[owner] + ccnt                
+                
+        if len(file.findall("ann")) == anno_per_file:
+            if fin:
+                finished_by_all = finished_by_all + 1
+            else:
+                fetched_by_all = fetched_by_all + 1  
+                
+        idx += 1
+        
+    
+    print fill("Login"), fill("Akt. pobr."), fill("Zwrocone (Napr.)"), fill("Zakonczone"), fill("Zak. znaki"), fill("Do pobrania")
+    print fill_custom("", 120, "-")
+    for user in set(current.keys()) | set(finished.keys()) | set(returned_fixed.keys()) | set(returned.keys()):
+        print fill(user), fill(str(current[user])), fill(str(returned[user]) + " (" + str(returned_fixed[user]) + ")"), fill(str(finished[user])),
+        print fill(str(chars[user])),
+        priority, normal = db.for_annotation(user)
+        print fill(str(len(normal)))
+                
+    print fill_custom("", 120, "-")
+    all_curr = sum([i for i in current.values()])
+    all_ret = sum([i for i in returned.values()])
+    all_ret_fixed = sum([i for i in returned_fixed.values()])    
+    all_fin = sum([i for i in finished.values()])
+    all_chars = sum([i for i in chars.values()])
+    print fill("Suma"), fill(str(all_curr)), fill(str(all_ret) + " (" + str(all_ret_fixed) + ")"), fill(str(all_fin)), fill(str(all_chars)), fill(str(all - all_curr - all_fin - all_ret))
+    print ""
+    print "Pobranych (nie zakonczonych) jednoczesnie przez", anno_per_file, "tekstow:", fetched_by_all
+    print "Zakonczonych jednoczesnie przez", anno_per_file, "tekstow:", finished_by_all
+    print "Pozostalo do pobrania naprawionych:", all_ret_fixed
+    print "Pozostalo do obejrzenia zwroconych:", all_ret - all_ret_fixed
+    print 
+    
+    return finished_by_all   
+    
+if __name__ == "__main__":
+    optparser = OptionParser(usage="""usage: %prog CONFIG""")
+    (options, args) = optparser.parse_args()
+    if len(args) < 1:
+        optparser.print_help()
+        sys.exit(0)
+        
+    conf_path = args[0]
+    cfg = Config(conf_path)
+    wc = Repo(cfg["svn.repository"], cfg["svn.login"], cfg["svn.passwd"])
+    db = Database(wc.db_path(), int(cfg["anno_per_file"]))    
+    anno_per_file = int(cfg["anno_per_file"])
+
+    rejected = count_rejected(db)  
+    print    
+    print fill_custom("", 100, "-")
+    all = len(db.file_index.keys())
+    print "Wszystkich tekstow w bazie: " + str(all)    
+    print "Odrzuconych: " + str(rejected)
+    print "Wszystkich tekstow w bazie bez odrzuconych: " + str(all - rejected)    
+    all = all * anno_per_file - rejected
+    print "Anotacji do wykonania: " + str(all)
+        
+    print fill_custom("", 100, "-")
+    print 
+    
+    print fill_custom("----Anotacja", 100, "-")
+    anno_count = count_anno_stats(db, all, anno_per_file)
+            
\ No newline at end of file
--
libgit2 0.22.2